diff --git a/Modules/Internal/Http/Controllers/Api/AppointmentController.php b/Modules/Internal/Http/Controllers/Api/AppointmentController.php new file mode 100644 index 00000000..3c8c5636 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/AppointmentController.php @@ -0,0 +1,88 @@ +with('doctor.user', 'doctor.speciality', 'appointmentDetail', 'healthCare') + ->paginate(15); + return response()->json(Helper::paginateResources(AppointmentResource::collection($appointments))); + } + /** + * 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) + { + $appointments = Appointment::query() + ->with('doctor.user', 'doctor.speciality', 'appointmentDetail', 'healthCare') + ->where('nID', $id) + ->first(); + return response()->json(new AppointmentResource($appointments)); + } + + /** + * 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) + { + // + } +} diff --git a/Modules/Internal/Http/Controllers/Api/LivechatController.php b/Modules/Internal/Http/Controllers/Api/LivechatController.php new file mode 100644 index 00000000..7a975a4b --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/LivechatController.php @@ -0,0 +1,89 @@ +where('nIDAppointment', '!=', null)->where('nIDAppointment', '!=', '') + ->paginate(15); + + return response()->json(Helper::paginateResources(LivechatResource::collection($livechat))); + } + + /** + * 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) + { + $livechat = Livechat::with('doctor.user', 'doctor.speciality', 'appointment.appointmentDetail', 'healthCare') + ->where('nIDAppointment', '!=', null)->where('nIDAppointment', '!=', '') + ->where('nID', $id) + ->first(); + return response()->json(new LivechatResource($livechat)); + } + + /** + * 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) + { + // + } +} diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 143727c9..11b289cc 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Api\MemberController as ApiMemberController; use Modules\Internal\Http\Controllers\Api\AuthController; use Illuminate\Http\Request; +use Modules\Internal\Http\Controllers\Api\AppointmentController; use Modules\Internal\Http\Controllers\Api\BenefitController; use Modules\Internal\Http\Controllers\Api\ClaimController; use Modules\Internal\Http\Controllers\Api\CorporateBenefitController; @@ -17,6 +18,7 @@ use Modules\Internal\Http\Controllers\Api\DivisionController; use Modules\Internal\Http\Controllers\Api\DoctorController; use Modules\Internal\Http\Controllers\Api\DrugController; use Modules\Internal\Http\Controllers\Api\FormulariumController; +use Modules\Internal\Http\Controllers\Api\LivechatController; use Modules\Internal\Http\Controllers\Api\MemberController; use Modules\Internal\Http\Controllers\Api\OrganizationController; use Modules\Internal\Http\Controllers\Api\PlanController; @@ -119,9 +121,12 @@ Route::prefix('internal')->group(function () { Route::get('search-organizations', [OrganizationController::class, 'searchOrganization']); Route::get('search-specialities', [SpecialityController::class, 'searchSpeciality']); Route::resource('organizations', OrganizationController::class); + Route::resource('appointments', AppointmentController::class); + Route::resource('live-chat', LivechatController::class); + Route::resource('doctors', DoctorController::class); - + Route::get('generate-log/{member_id}', [CorporateMemberController::class, 'generateLog']); }); diff --git a/Modules/Internal/Transformers/AppointmentResource.php b/Modules/Internal/Transformers/AppointmentResource.php new file mode 100644 index 00000000..180fd56c --- /dev/null +++ b/Modules/Internal/Transformers/AppointmentResource.php @@ -0,0 +1,45 @@ + $this->nID, + 'doctor_name' => isset($this->doctor->user->sFirstName) ? $this->doctor->user->sFirstName . ' ' . $this->doctor->user->sLastName : null, + 'speciality' => $this->doctor->speciality->sKeterangan, + 'date_appointment' => Carbon::parse($this->appointmentDetail->dTanggalAppointment)->format('d-m-Y') . ' ' . $this->appointmentDetail->tTimeAppointment, + 'date_created' => Carbon::parse($this->dCreateOn)->format('d-m-Y H:i:s') ?? null, + 'appointment_media' => $this->sMedia, + 'status' => $this->status_name, + 'health_care' => $this->healthCare->sHealthCare ?? null, + 'payment_method' => $this->payment_method ?? null, + ]; + + $payment_detail = null; + if ($this->appointmentDetail->sPaymentDetails != null) { + $payment_detail = [ + 'payment_type' => $this->appointmentDetail->sPaymentDetails['payment_type'], + 'transaction_time' => $this->appointmentDetail->sPaymentDetails['transaction_time'], + 'gross_amount' => $this->appointmentDetail->sPaymentDetails['gross_amount'], + 'currency' => $this->appointmentDetail->sPaymentDetails['currency'], + 'status_message' => $this->appointmentDetail->sPaymentDetails['status_message'], + ]; + } + + $appointment['payment_detail'] = $payment_detail; + + return $appointment; + } +} diff --git a/Modules/Internal/Transformers/LivechatResource.php b/Modules/Internal/Transformers/LivechatResource.php new file mode 100644 index 00000000..054a82de --- /dev/null +++ b/Modules/Internal/Transformers/LivechatResource.php @@ -0,0 +1,62 @@ + $this->nID, + 'doctor_name' => isset($this->doctor->user->sFirstName) ? $this->doctor->user->sFirstName . ' ' . $this->doctor->user->sLastName : null, + 'speciality' => $this->doctor->speciality->sKeterangan ?? null, + 'health_care' => $this->healthCare->sHealthCare ?? null, + 'date_appointment' => Carbon::parse($this->appointment->appointmentDetail->dTanggalAppointment)->format('d-m-Y') + . ' ' . $this->appointment->appointmentDetail->tTimeAppointment ?? null, + 'status_appointment' => $this->appointment->status_name ?? null, + 'date_created' => Carbon::parse($this->appointment->dCreateOn)->format('d-m-Y H:i:s') ?? null, + 'patient_media' => $this->sMedia ?? null, + 'doctor_media' => $this->sMediaDokter ?? null, + 'appointment_media' => $this->appointment->sMedia ?? null, + 'status_chat' => $this->status_name ?? null, + 'payment_method' => $this->appointment->payment_method ?? null, + ]; + + $start_time = $this->dStartTime; + $end_time = $this->dEndTime; + $data_duration = 0 . ' jam ' . 0 . ' menit ' . 0 . ' detik'; + if ($start_time != null && $end_time != null) { + $duration = Carbon::parse($start_time)->diffInMinutes(Carbon::parse($end_time)); + $hours = floor($duration / 60); + $minutes = $duration % 60; + $seconds = ($duration - ($hours * 60) - $minutes) * 60; + + $data_duration = $hours . ' jam ' . $minutes . ' menit ' . $seconds . ' detik'; + } + + $livechat['duration'] = $data_duration; + + $payment_detail = null; + if ($this->appointment->appointmentDetail->sPaymentDetails != null) { + $payment_detail = [ + 'payment_type' => $this->appointment->appointmentDetail->sPaymentDetails['payment_type'], + 'transaction_time' => $this->appointment->appointmentDetail->sPaymentDetails['transaction_time'], + 'gross_amount' => $this->appointment->appointmentDetail->sPaymentDetails['gross_amount'], + 'currency' => $this->appointment->appointmentDetail->sPaymentDetails['currency'], + 'status_message' => $this->appointment->appointmentDetail->sPaymentDetails['status_message'], + ]; + } + + $livechat['payment_detail'] = $payment_detail; + return $livechat; + } +} diff --git a/app/Models/OLDLMS/Appointment.php b/app/Models/OLDLMS/Appointment.php index 030ad6a6..1ec42aae 100644 --- a/app/Models/OLDLMS/Appointment.php +++ b/app/Models/OLDLMS/Appointment.php @@ -2,6 +2,7 @@ namespace App\Models\OLDLMS; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -14,12 +15,85 @@ class Appointment extends Model const UPDATED_AT = 'dUpdateOn'; const DELETED_AT = 'dDeleteOn'; + public $sStatusNames = [ + 0 => 'Menunggu Konfirmasi', + 1 => 'Diterima', + 2 => 'Ditolak', + 3 => 'Selesai', + 4 => 'Dibatalkan', + ]; + + public $sPaymentMethodName = [ + 1 => 'Transfer Bank', + 2 => 'Kartu Kredit', + 3 => 'Dompet Digital', + 4 => 'Gerai Retail', + 5 => 'QRIS', + ]; + protected $connection = 'oldlms'; protected $table = 'tx_appointment'; - public function detail() + protected $primaryKey = 'nID'; + + public $incrementing = false; + + protected $keyType = 'string'; + + protected $fillable = [ + 'nID', + 'nIDDokter', + 'nIDUser', + 'sStatus', + 'dCreateOn', + 'dUpdateOn', + 'dDeleteOn', + ]; + + + + protected $appends = [ + 'status_name', + 'payment_method' + ]; + + protected function StatusName(): Attribute { - return $this->hasOne(AppointmentDetail::class, ''); + return Attribute::make( + get: function ($value) { + return $this->sStatusNames[$this->sStatus] ?? '-'; + }, + ); + } + + protected function PaymentMethod(): Attribute + { + return Attribute::make( + get: function ($value) { + return $this->sPaymentMethodName[$this->sPaymentMethod] ?? '-'; + }, + ); + } + + + public function appointmentDetail() + { + return $this->hasOne(AppointmentDetail::class, 'nIDAppointment', 'nID'); + } + + public function doctor() + { + return $this->belongsTo(Dokter::class, 'nIDDokter', 'nID'); + } + + public function user() + { + return $this->belongsTo(User::class, 'nIDUser', 'nID'); + } + + public function healthCare() + { + return $this->belongsTo(Healthcare::class, 'nIDHealthCare', 'nID'); } } diff --git a/app/Models/OLDLMS/AppointmentDetail.php b/app/Models/OLDLMS/AppointmentDetail.php index 98212e1f..1cace77b 100644 --- a/app/Models/OLDLMS/AppointmentDetail.php +++ b/app/Models/OLDLMS/AppointmentDetail.php @@ -8,4 +8,19 @@ use Illuminate\Database\Eloquent\Model; class AppointmentDetail extends Model { use HasFactory; + + const CREATED_AT = 'dCreateOn'; + const UPDATED_AT = 'dUpdateOn'; + const DELETED_AT = 'dDeleteOn'; + + protected $connection = 'oldlms'; + + protected $table = 'tx_appointment_detail'; + protected $casts = [ + 'sPaymentDetails' => 'array', + ]; + public function appointment() + { + return $this->belongsTo(Appointment::class, 'nIDAppointment', 'nID'); + } } diff --git a/app/Models/OLDLMS/Dokter.php b/app/Models/OLDLMS/Dokter.php index 11929b23..51d33dae 100644 --- a/app/Models/OLDLMS/Dokter.php +++ b/app/Models/OLDLMS/Dokter.php @@ -24,4 +24,14 @@ class Dokter extends Model { return $this->hasMany(JadwalDokter::class, 'nIDDokter', 'nID'); } + + public function user() + { + return $this->belongsTo(User::class, 'nIDUser', 'nID'); + } + + public function speciality() + { + return $this->belongsTo(Speciality::class, 'nIDSpesialis', 'nID'); + } } diff --git a/app/Models/OLDLMS/Livechat.php b/app/Models/OLDLMS/Livechat.php new file mode 100644 index 00000000..af9c6301 --- /dev/null +++ b/app/Models/OLDLMS/Livechat.php @@ -0,0 +1,64 @@ + 'Menunggu Konfirmasi', + 1 => 'Diterima', + 2 => 'Ditolak', + 3 => 'Selesai', + 4 => 'Dibatalkan', + ]; + + const CREATED_AT = 'dCreateOn'; + const UPDATED_AT = 'dUpdateOn'; + const DELETED_AT = 'dDeleteOn'; + + protected $connection = 'oldlms'; + + protected $table = 'tx_livechat'; + + protected $appends = [ + 'status_name', + ]; + + protected function StatusName(): Attribute + { + return Attribute::make( + get: function ($value) { + return $this->sStatusNames[$this->sStatus] ?? '-'; + }, + ); + } + + + public function user() + { + return $this->belongsTo(User::class, 'nIDUser', 'nID'); + } + + public function doctor() + { + return $this->belongsTo(Dokter::class, 'nIDDokter', 'nID'); + } + + + public function appointment() + { + return $this->belongsTo(Appointment::class, 'nIDAppointment', 'nID'); + } + + public function healthCare() + { + return $this->belongsTo(Healthcare::class, 'nIDHealthCare', 'nID'); + } +} diff --git a/app/Models/OLDLMS/Speciality.php b/app/Models/OLDLMS/Speciality.php new file mode 100644 index 00000000..7c94b5c6 --- /dev/null +++ b/app/Models/OLDLMS/Speciality.php @@ -0,0 +1,26 @@ +hasMany(Dokter::class, 'nIDSpesialis', 'nID'); + } +} diff --git a/app/Models/OLDLMS/User.php b/app/Models/OLDLMS/User.php index 95a104a1..50b8f0a1 100644 --- a/app/Models/OLDLMS/User.php +++ b/app/Models/OLDLMS/User.php @@ -4,8 +4,17 @@ namespace App\Models\OLDLMS; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; class User extends Model { - use HasFactory; + use HasFactory, SoftDeletes; + + const CREATED_AT = 'dCreateOn'; + const UPDATED_AT = 'dUpdateOn'; + const DELETED_AT = 'dDeleteOn'; + + protected $connection = 'oldlms'; + + protected $table = 'tm_users'; } diff --git a/frontend/dashboard/.env.development b/frontend/dashboard/.env.development new file mode 100755 index 00000000..c429fda8 --- /dev/null +++ b/frontend/dashboard/.env.development @@ -0,0 +1,7 @@ +GENERATE_SOURCEMAP=false + +PORT=8083 + +REACT_APP_HOST_API_URL="http://localhost:8000" + +VITE_API_URL="http://localhost:8000/api/internal" diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index aa12a265..3f5b70a6 100755 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -69,6 +69,13 @@ const navConfig = [ title: 'CUSTOMER SERVICES', children: [{ title: 'Request', path: '/cs-request' }], }, + { + title: 'REPORT', + children: [ + { title: 'Appointment', path: '/report/appointments' }, + { title: 'Live Chat', path: '/report/live-chat' }, + ], + }, { title: 'USER MANAGEMENT', path: '/users', diff --git a/frontend/dashboard/src/pages/Report/Appointments/Create.tsx b/frontend/dashboard/src/pages/Report/Appointments/Create.tsx new file mode 100644 index 00000000..b0f07577 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Appointments/Create.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import Form from './Form'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Practitioner } from '../../../@types/doctor'; +import ButtonBack from '../../../components/ButtonBack'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentPractitioner, setCurrentPractitioner] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/doctors/' + id).then((res) => { + setCurrentPractitioner(res.data); + }); + } + }, [id]); + + return ( + + + + + + + +
+ + + ); +} +// const pageTitle = 'Create Data Dokter'; +// return ( +// +// +// + +// +// +// +// +// +// +// +// +// +// ); +// } diff --git a/frontend/dashboard/src/pages/Report/Appointments/Form.tsx b/frontend/dashboard/src/pages/Report/Appointments/Form.tsx new file mode 100644 index 00000000..39885db8 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Appointments/Form.tsx @@ -0,0 +1,260 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Practitioner } from '../../../@types/doctor'; + +import { Label, Rowing } from '@mui/icons-material'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentPractitioner?: Practitioner; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function PractitionerForm({ isEdit, currentPractitioner }: Props) { + const navigate = useNavigate(); + const [practitioner_group, setPractitionerGroups] = useState([]); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentPractitioner?.id, + name: currentPractitioner?.name || '', + address: currentPractitioner?.address || '', + birth_date: currentPractitioner?.birth_date || '', + gender: currentPractitioner?.gender || '', + description: currentPractitioner?.description || '', + birth_place: currentPractitioner?.birth_place || '', + active: currentPractitioner?.active === 1 ? true : false, + avatar_url: currentPractitioner?.avatar_url || '', + doctor_id: currentPractitioner?.doctor_id || '', + organizations: currentPractitioner?.organizations || [], + specialities: currentPractitioner?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentPractitioner] + ); + + console.log('defaultValues', defaultValues); + + function StatusLabel({ value }: { value: boolean }) { + return ( + + ); + } + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentPractitioner) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentPractitioner]); + + const handleActivate = (event: React.ChangeEvent) => { + setValue('active', event.target.checked); + + console.log('event.target.checked', event.target.checked); + + const formData = new FormData(); + formData.append('active', event.target.checked ? '1' : '0'); + formData.append('_method', 'PUT'); + axios.post('/doctors/' + currentPractitioner?.id ?? '', formData); + + enqueueSnackbar('active Updated Successfully!', { variant: 'success' }); + }; + + return ( + + + + {/* */} + + + + Data Dokter + + + {/* Status Rumah Sakit */} + + + + + Informasi Umum + + + + + Nama Dokter + {currentPractitioner?.name ? currentPractitioner?.name : '-'} + No Telp + {currentPractitioner?.phone ? currentPractitioner?.phone : '-'} + Tempat Lahir + + {currentPractitioner?.birth_place ? currentPractitioner?.birth_place : '-'} + + Alamat + {currentPractitioner?.address ? currentPractitioner?.address : '-'} + + + Jenis Kelamin + {currentPractitioner?.gender ? currentPractitioner?.gender : '-'} + Email + {currentPractitioner?.email ? currentPractitioner?.email : '-'} + Tanggal Lahir + + {currentPractitioner?.birth_date ? currentPractitioner?.birth_date : '-'} + + + + + + Tempat Praktik + {currentPractitioner?.organizations?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + Spesialisasi + {currentPractitioner?.specialities?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Appointments/Index.tsx b/frontend/dashboard/src/pages/Report/Appointments/Index.tsx new file mode 100644 index 00000000..a63bf158 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Appointments/Index.tsx @@ -0,0 +1,35 @@ +import { Card, Grid, Container } from '@mui/material'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import Page from '../../../components/Page'; +import useSettings from '../../../hooks/useSettings'; +import List from './List'; + +export default function Doctors() { + const { themeStretch } = useSettings(); + + const { id } = useParams(); + + const pageTitle = 'Appointments'; + return ( + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Appointments/List.tsx b/frontend/dashboard/src/pages/Report/Appointments/List.tsx new file mode 100644 index 00000000..c26fcbbf --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Appointments/List.tsx @@ -0,0 +1,389 @@ +import { + Box, + Button, + Card, + Collapse, + Paper, + Select, + SelectChangeEvent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Stack, + ButtonGroup, + Grid, + Chip, + Dialog, + DialogContent, + DialogContentText, + DialogActions, + FormControl, + Autocomplete, + InputAdornment, + IconButton, +} from '@mui/material'; + +import { + Link, + NavLink as RouterLink, + useSearchParams, + useNavigate, + useParams, +} from 'react-router-dom'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +// components +import axios from '../../../utils/axios'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import { Icd } from '../../../@types/diagnosis'; +import BasePagination from '../../../components/BasePagination'; +import { Practitioner } from '../../../@types/doctor'; +import CreateIcon from '@mui/icons-material/Create'; +import { Props } from '../../../components/editor/index'; +import { red } from '@mui/material/colors'; +import { margin, padding } from '@mui/system'; +import { enqueueSnackbar } from 'notistack'; +import { Controller } from 'react-hook-form'; + +import SvgIconStyle from '../../../components/SvgIconStyle'; +import { GridSearchIcon } from '@mui/x-data-grid'; +import { Search } from '@mui/icons-material'; +import { Icon } from '@iconify/react'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +// ---------------------------------------------------------------------- + +export default function List() { + // Generate the every row of the table + + const navigate = useNavigate(); + const { organization_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [searchParamsOrganizations, setSearchParamsOrganizations] = useSearchParams(); + const [searchParamsSpecialities, setSearchParamsSpecialities] = useSearchParams(); + const [searchParamsFilter, setSearchParamsFilter] = useSearchParams(); + + function Filter(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(''); + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + + props.onSearch(searchText); + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + return ( + + + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + value={searchText} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search', + }} + /> + + + + ); + } + + function FilterForm(props: any) { + // IMPORT + return ( + + + + + + ); + } + + function createData(doctor: Practitioner): Practitioner { + return { + ...doctor, + }; + } + + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + const [openDialog, setOpenDialog] = React.useState(false); + + const handleDelete = (model: any) => { + axios + .delete(`/doctors/${row.id}`) + .then((res) => { + setDataTableData({ + ...dataTableData, + data: dataTableData.data.filter((model) => model.id != row.id), + }); + enqueueSnackbar('Data berhasil dihapus', { variant: 'success' }); + }) + .catch((error) => { + enqueueSnackbar( + error.response.data.message ?? error.message ?? 'Failed Processing Request', + { variant: 'error' } + ); + }); + }; + + return ( + + + + {row.date_created ? row.date_created : '-'} + {row.date_appointment ? row.date_appointment : '-'} + {row.health_care ? row.health_care : '-'} + {row.doctor_name ? row.doctor_name : '-'} + {row.speciality ? row.speciality : '-'} + {row.appointment_media ? row.appointment_media : '-'} + + {row.status ? row.status : '-'} + + + + + + + + + + { + setOpenDialog(false); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + + + Apakah anda yakin ingin menghapus + + + {row.name}? + + + + + + + + + ); + } + + const headStyle = { + fontWeight: 'bold', + }; + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableLastRequest, setDataTableLastRequest] = useState(0); + const [dataTableResponseState, setDataTableResponseState] = useState('idle'); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: '', + first_page_url: '', + last_page: 1, + last_page_url: '', + next_page_url: '', + prev_page_url: '', + per_page: 10, + from: 0, + to: 0, + total: 0, + }); + const [dataTablePage, setDataTablePage] = useState(5); + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/appointments', { + params: filter, + }); + setDataTableLoading(false); + setDataTableData(response.data); + }; + + // const applyFilter = async (searchFilter: string) => { + // await loadDataTableData({ search: searchFilter }); + // setSearchParams({ search: searchFilter }); + // }; + + const applyItems = async ( + searchFilter: string, + searchFilterOrganization: string, + searchFilterSpecialities: string + ) => { + await loadDataTableData({ + search: searchFilter, + organization_id: searchFilterOrganization, + speciality_id: searchFilterSpecialities, + }); + setSearchParamsFilter({ + search: searchFilter, + organization_id: searchFilterOrganization, + speciality_id: searchFilterSpecialities, + }); + }; + + const handlePageChange = (event: ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; + + useEffect(() => { + loadDataTableData(); + }, []); + + return ( + + {/* */} + + + + + {/* The Main Table */} + + + + + + + Tanggal Booking + + + Tanggal Appointment + + + Faskes + + + Nama Dokter + + + Spesialisasi + + + Appointment Via App/Website + + + Status Appointment + + + + {/* + Aksi + */} + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} +
+
+ + +
+
+ ); +} diff --git a/frontend/dashboard/src/pages/Report/Appointments/Show.tsx b/frontend/dashboard/src/pages/Report/Appointments/Show.tsx new file mode 100644 index 00000000..be9d1c46 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Appointments/Show.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import View from './View'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Appointment } from '../../../@types/doctor'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentAppointment, setCurrentAppointment] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/appointments/' + id).then((res) => { + setCurrentAppointment(res.data); + }); + } + }, [id]); + + return ( + + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Appointments/View.tsx b/frontend/dashboard/src/pages/Report/Appointments/View.tsx new file mode 100644 index 00000000..8105b8b1 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Appointments/View.tsx @@ -0,0 +1,275 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, + Badge, + Divider, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Appointment } from '../../../@types/doctor'; + +import { Label, Rowing, Spa } from '@mui/icons-material'; +import { border } from '@mui/system'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentAppointment?: Appointment; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function AppointmentForm({ isEdit, currentAppointment }: Props) { + const navigate = useNavigate(); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentAppointment?.id, + name: currentAppointment?.name || '', + address: currentAppointment?.address || '', + birth_date: currentAppointment?.birth_date || '', + gender: currentAppointment?.gender || '', + description: currentAppointment?.description || '', + birth_place: currentAppointment?.birth_place || '', + active: currentAppointment?.active === 1 ? true : false, + avatar_url: currentAppointment?.avatar_url || '', + doctor_id: currentAppointment?.doctor_id || '', + organizations: currentAppointment?.organizations || [], + specialities: currentAppointment?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentAppointment] + ); + + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentAppointment) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentAppointment]); + + return ( + + + + {/* */} + + + + } + spacing={2} + > + Data Appointment + + + + + + + + + + + Tanggal Booking : + + {currentAppointment?.date_created ? currentAppointment?.date_created : '-'} + + + + + + Tanggal Appointment : + + {currentAppointment?.date_appointment + ? currentAppointment?.date_appointment + : '-'} + + + + + + + Nama Dokter + + {currentAppointment?.doctor_name ? currentAppointment?.doctor_name : '-'} + + Faskes + + {currentAppointment?.health_care ? currentAppointment?.health_care : '-'} + + + + Spesialis + {currentAppointment?.speciality ? currentAppointment?.speciality : '-'} + Appointment Via Web/App + + {currentAppointment?.appointment_media + ? currentAppointment?.appointment_media + : '-'} + + + + + + + + Data Pembayaran + + + + {currentAppointment?.payment_detail !== null ? ( + + + Metode Pembayaran + + {currentAppointment?.payment_method ? currentAppointment?.payment_method : '-'} + + Harga + + {currentAppointment?.payment_detail?.gross_amount + ? currentAppointment?.payment_detail?.gross_amount + : '-'} + + Mata Uang + + {currentAppointment?.payment_detail?.currency + ? currentAppointment?.payment_detail?.currency + : '-'} + + + + Tipe Pembayaran + + {currentAppointment?.payment_detail?.payment_type + ? currentAppointment?.payment_detail?.payment_type + : '-'} + + Waktu Transaksi + + {currentAppointment?.payment_detail?.transaction_time + ? currentAppointment?.payment_detail?.transaction_time + : '-'} + + Status + + {currentAppointment?.payment_detail?.status_message + ? currentAppointment?.payment_detail?.status_message + : '-'} + + + + ) : ( + Belum ada pembayaran + )} + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Livechat/Create.tsx b/frontend/dashboard/src/pages/Report/Livechat/Create.tsx new file mode 100644 index 00000000..b0f07577 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Livechat/Create.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import Form from './Form'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Practitioner } from '../../../@types/doctor'; +import ButtonBack from '../../../components/ButtonBack'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentPractitioner, setCurrentPractitioner] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/doctors/' + id).then((res) => { + setCurrentPractitioner(res.data); + }); + } + }, [id]); + + return ( + + + + + + + +
+ + + ); +} +// const pageTitle = 'Create Data Dokter'; +// return ( +// +// +// + +// +// +// +// +// +// +// +// +// +// ); +// } diff --git a/frontend/dashboard/src/pages/Report/Livechat/Form.tsx b/frontend/dashboard/src/pages/Report/Livechat/Form.tsx new file mode 100644 index 00000000..39885db8 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Livechat/Form.tsx @@ -0,0 +1,260 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Practitioner } from '../../../@types/doctor'; + +import { Label, Rowing } from '@mui/icons-material'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentPractitioner?: Practitioner; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function PractitionerForm({ isEdit, currentPractitioner }: Props) { + const navigate = useNavigate(); + const [practitioner_group, setPractitionerGroups] = useState([]); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentPractitioner?.id, + name: currentPractitioner?.name || '', + address: currentPractitioner?.address || '', + birth_date: currentPractitioner?.birth_date || '', + gender: currentPractitioner?.gender || '', + description: currentPractitioner?.description || '', + birth_place: currentPractitioner?.birth_place || '', + active: currentPractitioner?.active === 1 ? true : false, + avatar_url: currentPractitioner?.avatar_url || '', + doctor_id: currentPractitioner?.doctor_id || '', + organizations: currentPractitioner?.organizations || [], + specialities: currentPractitioner?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentPractitioner] + ); + + console.log('defaultValues', defaultValues); + + function StatusLabel({ value }: { value: boolean }) { + return ( + + ); + } + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentPractitioner) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentPractitioner]); + + const handleActivate = (event: React.ChangeEvent) => { + setValue('active', event.target.checked); + + console.log('event.target.checked', event.target.checked); + + const formData = new FormData(); + formData.append('active', event.target.checked ? '1' : '0'); + formData.append('_method', 'PUT'); + axios.post('/doctors/' + currentPractitioner?.id ?? '', formData); + + enqueueSnackbar('active Updated Successfully!', { variant: 'success' }); + }; + + return ( + + + + {/* */} + + + + Data Dokter + + + {/* Status Rumah Sakit */} + + + + + Informasi Umum + + + + + Nama Dokter + {currentPractitioner?.name ? currentPractitioner?.name : '-'} + No Telp + {currentPractitioner?.phone ? currentPractitioner?.phone : '-'} + Tempat Lahir + + {currentPractitioner?.birth_place ? currentPractitioner?.birth_place : '-'} + + Alamat + {currentPractitioner?.address ? currentPractitioner?.address : '-'} + + + Jenis Kelamin + {currentPractitioner?.gender ? currentPractitioner?.gender : '-'} + Email + {currentPractitioner?.email ? currentPractitioner?.email : '-'} + Tanggal Lahir + + {currentPractitioner?.birth_date ? currentPractitioner?.birth_date : '-'} + + + + + + Tempat Praktik + {currentPractitioner?.organizations?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + Spesialisasi + {currentPractitioner?.specialities?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Livechat/Index.tsx b/frontend/dashboard/src/pages/Report/Livechat/Index.tsx new file mode 100644 index 00000000..8d02c936 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Livechat/Index.tsx @@ -0,0 +1,35 @@ +import { Card, Grid, Container } from '@mui/material'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import Page from '../../../components/Page'; +import useSettings from '../../../hooks/useSettings'; +import List from './List'; + +export default function Doctors() { + const { themeStretch } = useSettings(); + + const { id } = useParams(); + + const pageTitle = 'Live Chat'; + return ( + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Livechat/List.tsx b/frontend/dashboard/src/pages/Report/Livechat/List.tsx new file mode 100644 index 00000000..c5b56840 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Livechat/List.tsx @@ -0,0 +1,466 @@ +import { + Box, + Button, + Card, + Collapse, + Paper, + Select, + SelectChangeEvent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Stack, + ButtonGroup, + Grid, + Chip, + Dialog, + DialogContent, + DialogContentText, + DialogActions, + FormControl, + Autocomplete, + InputAdornment, + IconButton, +} from '@mui/material'; + +import { + Link, + NavLink as RouterLink, + useSearchParams, + useNavigate, + useParams, +} from 'react-router-dom'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +// components +import axios from '../../../utils/axios'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import { Icd } from '../../../@types/diagnosis'; +import BasePagination from '../../../components/BasePagination'; +import { Practitioner } from '../../../@types/doctor'; +import CreateIcon from '@mui/icons-material/Create'; +import { Props } from '../../../components/editor/index'; +import { red } from '@mui/material/colors'; +import { margin, padding } from '@mui/system'; +import { enqueueSnackbar } from 'notistack'; +import { Controller } from 'react-hook-form'; + +import SvgIconStyle from '../../../components/SvgIconStyle'; +import { GridSearchIcon } from '@mui/x-data-grid'; +import { Search } from '@mui/icons-material'; +import { Icon } from '@iconify/react'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +// ---------------------------------------------------------------------- + +export default function List() { + // Generate the every row of the table + + const navigate = useNavigate(); + const { organization_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [searchParamsOrganizations, setSearchParamsOrganizations] = useSearchParams(); + const [searchParamsSpecialities, setSearchParamsSpecialities] = useSearchParams(); + const [searchParamsFilter, setSearchParamsFilter] = useSearchParams(); + + function Filter(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(''); + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + + props.onSearch(searchText); + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + return ( + + + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + value={searchText} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search', + }} + /> + + + + ); + } + + function FilterForm(props: any) { + // IMPORT + return ( + + + + + + ); + } + + function createData(doctor: Practitioner): Practitioner { + return { + ...doctor, + }; + } + + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + const [openDialog, setOpenDialog] = React.useState(false); + + const handleDelete = (model: any) => { + axios + .delete(`/doctors/${row.id}`) + .then((res) => { + setDataTableData({ + ...dataTableData, + data: dataTableData.data.filter((model) => model.id != row.id), + }); + enqueueSnackbar('Data berhasil dihapus', { variant: 'success' }); + }) + .catch((error) => { + enqueueSnackbar( + error.response.data.message ?? error.message ?? 'Failed Processing Request', + { variant: 'error' } + ); + }); + }; + + return ( + + + + setOpen(!open)}> + {open ? : } + + + {row.date_created ? row.date_created : '-'} + {row.date_appointment ? row.date_appointment : '-'} + {row.health_care ? row.health_care : '-'} + {row.doctor_name ? row.doctor_name : '-'} + {row.speciality ? row.speciality : '-'} + {row.appointment_media ? row.appointment_media : '-'} + {row.patient_media ? row.patient_media : '-'} + {row.doctor_media ? row.doctor_media : '-'} + + {row.status_appointment ? row.status_appointment : '-'} + + {row.status_chat ? row.status_chat : '-'} + + + + + + + + + {/* COLLAPSIBLE ROW */} + + + + + + + + + Metode Pembayaran + + + : {row.payment_method ? row.payment_method : '-'} + + + + Jenis Benefit + + + : - + + + Durasi + + + : {row.duration ? row.duration : '-'} + + + + + + + + + + {/* END COLLAPSIBLE ROW */} + { + setOpenDialog(false); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + + + Apakah anda yakin ingin menghapus + + + {row.name}? + + + + + + + + + ); + } + + const headStyle = { + fontWeight: 'bold', + }; + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableLastRequest, setDataTableLastRequest] = useState(0); + const [dataTableResponseState, setDataTableResponseState] = useState('idle'); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: '', + first_page_url: '', + last_page: 1, + last_page_url: '', + next_page_url: '', + prev_page_url: '', + per_page: 10, + from: 0, + to: 0, + total: 0, + }); + const [dataTablePage, setDataTablePage] = useState(5); + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/live-chat', { + params: filter, + }); + setDataTableLoading(false); + setDataTableData(response.data); + }; + + // const applyFilter = async (searchFilter: string) => { + // await loadDataTableData({ search: searchFilter }); + // setSearchParams({ search: searchFilter }); + // }; + + const applyItems = async ( + searchFilter: string, + searchFilterOrganization: string, + searchFilterSpecialities: string + ) => { + await loadDataTableData({ + search: searchFilter, + organization_id: searchFilterOrganization, + speciality_id: searchFilterSpecialities, + }); + setSearchParamsFilter({ + search: searchFilter, + organization_id: searchFilterOrganization, + speciality_id: searchFilterSpecialities, + }); + }; + + const handlePageChange = (event: ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; + + useEffect(() => { + loadDataTableData(); + }, []); + + return ( + + {/* */} + + + + + {/* The Main Table */} + + + + + {/* */} + + + Tanggal Booking + + + Tanggal Appointment + + + Faskes + + + Nama Dokter + + + Spesialisasi + + + Appointment Via App/Website + + + Chat Via App/Website + + + Status Appointment + + + Status Chat + + + + + {/* + Tanggal Booking + + + Tanggal Appointment + + + Faskes + + + Nama Dokter + + + Spesialisasi + */} + + Pasien + + + Dokter + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} +
+
+ + +
+
+ ); +} diff --git a/frontend/dashboard/src/pages/Report/Livechat/Show.tsx b/frontend/dashboard/src/pages/Report/Livechat/Show.tsx new file mode 100644 index 00000000..1514d9b8 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Livechat/Show.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import View from './View'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Appointment } from '../../../@types/doctor'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentAppointment, setCurrentAppointment] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/live-chat/' + id).then((res) => { + setCurrentAppointment(res.data); + }); + } + }, [id]); + + return ( + + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Livechat/View.tsx b/frontend/dashboard/src/pages/Report/Livechat/View.tsx new file mode 100644 index 00000000..29065288 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Livechat/View.tsx @@ -0,0 +1,309 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, + Badge, + Divider, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Appointment } from '../../../@types/doctor'; + +import { Label, Rowing, Spa } from '@mui/icons-material'; +import { border, padding } from '@mui/system'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentAppointment?: Appointment; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function AppointmentForm({ isEdit, currentAppointment }: Props) { + const navigate = useNavigate(); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentAppointment?.id, + name: currentAppointment?.name || '', + address: currentAppointment?.address || '', + birth_date: currentAppointment?.birth_date || '', + gender: currentAppointment?.gender || '', + description: currentAppointment?.description || '', + birth_place: currentAppointment?.birth_place || '', + active: currentAppointment?.active === 1 ? true : false, + avatar_url: currentAppointment?.avatar_url || '', + doctor_id: currentAppointment?.doctor_id || '', + organizations: currentAppointment?.organizations || [], + specialities: currentAppointment?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentAppointment] + ); + + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentAppointment) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentAppointment]); + + return ( + + + + {/* */} + + + + } + spacing={2} + > + Data Live Chat + + + + + + + + + + + Status Appointment : + + + + + + + + Status Chat : + + + + + + + + + + + Tanggal Booking : + + {currentAppointment?.date_created ? currentAppointment?.date_created : '-'} + + + + + + Tanggal Appointment : + + {currentAppointment?.date_appointment + ? currentAppointment?.date_appointment + : '-'} + + + + + + + + Nama Dokter + + {currentAppointment?.doctor_name ? currentAppointment?.doctor_name : '-'} + + Faskes + + {currentAppointment?.health_care ? currentAppointment?.health_care : '-'} + + Durasi + {currentAppointment?.duration ? currentAppointment?.duration : '-'} + + + Spesialis + {currentAppointment?.speciality ? currentAppointment?.speciality : '-'} + Appointment Via Web/App + + {currentAppointment?.appointment_media + ? currentAppointment?.appointment_media + : '-'} + + + + + + + + Data Pembayaran + + + + {currentAppointment?.payment_detail !== null ? ( + + + Metode Pembayaran + + {currentAppointment?.payment_method ? currentAppointment?.payment_method : '-'} + + Harga + + {currentAppointment?.payment_detail?.gross_amount + ? currentAppointment?.payment_detail?.gross_amount + : '-'} + + Mata Uang + + {currentAppointment?.payment_detail?.currency + ? currentAppointment?.payment_detail?.currency + : '-'} + + + + Tipe Pembayaran + + {currentAppointment?.payment_detail?.payment_type + ? currentAppointment?.payment_detail?.payment_type + : '-'} + + Waktu Transaksi + + {currentAppointment?.payment_detail?.transaction_time + ? currentAppointment?.payment_detail?.transaction_time + : '-'} + + Status + + {currentAppointment?.payment_detail?.status_message + ? currentAppointment?.payment_detail?.status_message + : '-'} + + + + ) : ( + Belum ada pembayaran + )} + + + + + ); +} diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index bd77a22d..ca444827 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -217,6 +217,39 @@ export default function Router() { element: , }, + { + path: 'report/appointments', + element: , + }, + { + path: 'report/appointments/:id', + element: , + }, + { + path: 'report/appointments/:id/show', + element: , + }, + { + path: 'report/appointments/:id/edit', + element: , + }, + { + path: 'report/live-chat', + element: , + }, + { + path: 'report/live-chat/:id', + element: , + }, + { + path: 'report/live-chat/:id/show', + element: , + }, + { + path: 'report/live-chat/:id/edit', + element: , + }, + { path: 'claims', element: , @@ -322,6 +355,14 @@ const MasterDiagnosis = Loadable(lazy(() => import('../pages/Master/Diagnosis/In const MasterDoctors = Loadable(lazy(() => import('../pages/Master/Doctors/Index'))); const MasterHospitals = Loadable(lazy(() => import('../pages/Master/Hospitals/Index'))); +const Appointment = Loadable(lazy(() => import('../pages/Report/Appointments/Index'))); +const AppointmentCreate = Loadable(lazy(() => import('../pages/Report/Appointments/Create'))); +const AppointmentShow = Loadable(lazy(() => import('../pages/Report/Appointments/Show'))); + +const Livechat = Loadable(lazy(() => import('../pages/Report/Livechat/Index'))); +const LivechatCreate = Loadable(lazy(() => import('../pages/Report/Livechat/Create'))); +const LivechatShow = Loadable(lazy(() => import('../pages/Report/Livechat/Show'))); + const MasterDrug = Loadable(lazy(() => import('../pages/Master/Drug/Index'))); const MasterFormularium = Loadable(lazy(() => import('../pages/Master/Formularium/Index')));