diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index 3e4513cc..6fb49e19 100644 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -14,6 +14,10 @@ use Modules\Internal\Http\Controllers\ClaimEncounterController; use Modules\Client\Http\Controllers\Api\ClaimReportController; use Modules\Client\Http\Controllers\Api\ClaimRequestController; use Modules\Client\Http\Controllers\Api\DataController; +use Modules\Internal\Http\Controllers\Api\FormulariumController; +use Modules\Internal\Http\Controllers\Api\FormulariumTemplateController; +use Modules\Internal\Http\Controllers\Api\AuditTrailController; +use Modules\Internal\Http\Controllers\Api\CorporateController; /* |-------------------------------------------------------------------------- @@ -71,5 +75,20 @@ Route::prefix('client')->group(function () { Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store'); Route::post('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show'); + + Route::get('master/formulariums/{formulariums_template_id}', [FormulariumController::class, 'index']); + Route::post('master/formulariums/{formulariums_template_id}', [FormulariumController::class, 'store']); + Route::post('master/formulariums/{formulariums_template_id}/import', [FormulariumController::class, 'import']); + Route::get('master/formulariums/{formulariums_template_id}/list', [FormulariumController::class, 'generateFormulariumList']); + + Route::get('master/formularium-template', [FormulariumTemplateController::class, 'index']); + Route::get('master/formularium-template/search', [FormulariumTemplateController::class, 'search']); + Route::post('master/formularium-template/store', [FormulariumTemplateController::class, 'store']); + Route::put('master/formularium-template/{id}/activation', [FormulariumTemplateController::class, 'activation']); + Route::get('master/formularium-template/{id}/edit', [FormulariumTemplateController::class, 'edit']); + Route::put('master/formularium-template/{id}/update', [FormulariumTemplateController::class, 'update']); + + Route::get('audittrail/{corporate_id}', [AuditTrailController::class, 'index']); + Route::get('corporates/import-document-example/{document_type}', [CorporateController::class, 'importDocumentExample']); }); }); diff --git a/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php b/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php index 96a37f18..18520429 100644 --- a/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php +++ b/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php @@ -24,20 +24,41 @@ use Modules\Internal\Services\IcdService; class FormulariumTemplateController extends Controller { public function index(Request $request) - { - if ($request->search){ - return FormulariumTemplate::when($request->search ?? null, function($icd, $search) { - $icd->where('name', 'LIKE', '%'.$search.'%') - ->orWhere('description', 'LIKE', '%'.$search.'%'); - })->paginate(15); - } else { - $diagnosisTemplate = FormulariumTemplate::query() - // ->filter($request->toArray()) - ->orderBy('name', 'ASC') - ->paginate(15); - return $diagnosisTemplate; + { if($request->corporate_id) + { + if ($request->search){ + return FormulariumTemplate::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('description', 'LIKE', '%'.$search.'%'); + }) + ->join('corporate_formulariums', 'formularium_templates.id', '=', 'corporate_formulariums.formularium_template_id') + ->where('corporate_formulariums.corporate_id', '=', $request->corporate_id) + ->paginate(15); + } else { + $diagnosisTemplate = FormulariumTemplate::query() + // ->filter($request->toArray()) + ->join('corporate_formulariums', 'formularium_templates.id', '=', 'corporate_formulariums.formularium_template_id') + ->where('corporate_formulariums.corporate_id', '=', $request->corporate_id) + ->orderBy('name', 'ASC') + ->paginate(15); + return $diagnosisTemplate; + } + } + else + { + if ($request->search){ + return FormulariumTemplate::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('description', 'LIKE', '%'.$search.'%'); + })->paginate(15); + } else { + $diagnosisTemplate = FormulariumTemplate::query() + // ->filter($request->toArray()) + ->orderBy('name', 'ASC') + ->paginate(15); + return $diagnosisTemplate; + } } - } /** @@ -127,7 +148,7 @@ class FormulariumTemplateController extends Controller })->limit(10)->get(); } - public function import(Request $request) + public function import(Request $request) { $request->validate([ 'file' => 'required|file|mimes:xls,xlsx,csv,txt', @@ -171,7 +192,7 @@ class FormulariumTemplateController extends Controller 6 => 'version', 7 => 'active', ]; - + foreach ($row->getCells() as $header_index => $cell) { if (isset($row_map[$header_index])) { $value = $cell->getValue(); @@ -246,7 +267,7 @@ class FormulariumTemplateController extends Controller public function activation(Request $request, $id) { - + $request->validate([ 'active' => 'required' ]); @@ -268,20 +289,20 @@ class FormulariumTemplateController extends Controller // Membuat penulis entitas Spout $writer = WriterEntityFactory::createXLSXWriter(); - + // Membuka penulis untuk menulis ke file $writer->openToFile(public_path('files/CorporateMembershipList.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) { @@ -295,22 +316,22 @@ class FormulariumTemplateController extends Controller $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/CorporateMembershipList.xlsx'); - + return Helper::responseJson([ 'file_name' => "Diagnosis ICD List " . date('Y-m-d h:i:s'), "file_url" => url('files/CorporateMembershipList.xlsx') ]); - + } } diff --git a/frontend/client-portal/src/components/DialogUpdateStatus.tsx b/frontend/client-portal/src/components/DialogUpdateStatus.tsx new file mode 100644 index 00000000..71b8f908 --- /dev/null +++ b/frontend/client-portal/src/components/DialogUpdateStatus.tsx @@ -0,0 +1,208 @@ +import * as Yup from 'yup'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Dialog, DialogTitle, DialogContent, Stack, Typography, IconButton, Grid } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { ReactElement } from 'react'; +import Iconify from './Iconify'; +import { Card } from '@mui/material'; +import { FormProvider, RHFTextField, RHFSwitch, RHFSelect } from './hook-form'; +import { Button } from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import axios from '@/utils/axios'; +import { enqueueSnackbar } from 'notistack'; + +// ---------------------------------------------------------------------- + +type DataContent = { + code: string; + name: string; + id: number; + status: string +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + maxWidth?: string; + data?: DataContent | undefined; + description: string; +}; + +type FormValuesProps = { + value: string; + active: boolean; +}; + + +// ---------------------------------------------------------------------- + +const DialogUpdateStatus = ({ title, openDialog, setOpenDialog, data, maxWidth, content }: MuiDialogProps) => { + const NewCorporateSchema = Yup.object().shape({ + reason: Yup.string().required('Corporate Status is required'), + }); + + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + }); + + const { + reset, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + + useEffect(() => { + if (openDialog === false) { + reset(); + } + }, [openDialog, reset]); + + const handleClose = () => { + setOpenDialog(false); + }; + + const handleUpdate = (id: number, status: number) => { + axios + .put(`/corporates/${id}/activation`, { + // service_code: service.service_code, + active: status, + }) + .then((res) => { + handleClose() + window.location.reload(); + }) + .catch((error) => { + // console.log('asdasd', error.response.data.message) + enqueueSnackbar( + error.response.data.message ?? error.message ?? 'Failed Processing Request', + { variant: 'error' } + ); + }); + } + let maxWidthDialog = 'md'; + + if (maxWidth) { + maxWidthDialog = maxWidth; + } + + const onSubmit = async (row : any) => { + console.log('test') + }; + + return ( + + + + {title?.icon ? ( + + + {title?.name} + + ) : ( + {title?.name ? title?.name : ''} + )} + + + + + + + + {/* + {description} + + + + + Code + + + {data?.code} + + + Corporate Name + + + {data?.name} + + + + + + Reason for update* + + + + + + + + + + + + + + + {data?.status == 1 ? + + : } + + */} + {content} + + + + ); +}; + +export default DialogUpdateStatus; diff --git a/frontend/client-portal/src/contexts/ConfiguredCorporateContext.tsx b/frontend/client-portal/src/contexts/ConfiguredCorporateContext.tsx new file mode 100644 index 00000000..59270cf0 --- /dev/null +++ b/frontend/client-portal/src/contexts/ConfiguredCorporateContext.tsx @@ -0,0 +1,46 @@ +import { Corporate } from '@/@types/corporates'; +import axios from '@/utils/axios'; +import { ReactNode, createContext, useState, useEffect } from 'react'; +import { useParams } from 'react-router'; +// hooks +import useResponsive from '../hooks/useResponsive'; + +// ---------------------------------------------------------------------- + +export type ConfiguredCorporateContextProps = { + currentCorporate?: Corporate | null; +}; + +const initialState: ConfiguredCorporateContextProps = { + currentCorporate: null, +}; + +const ConfiguredCorporateContext = createContext(initialState); + +type ConfiguredCorporateProviderProps = { + children: ReactNode; +}; + +function ConfiguredCorporateProvider({ children }: ConfiguredCorporateProviderProps) { + const { corporate_id } = useParams(); + const [corporate, setCorporate] = useState(null); + + useEffect(() => { + axios.get(`corporates/${corporate_id}`) + .then((res) => { + setCorporate(res.data) + }) + }, []); + + return ( + + {children} + + ); +} + +export { ConfiguredCorporateProvider, ConfiguredCorporateContext }; diff --git a/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx index b910d105..4ea27c39 100644 --- a/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx @@ -32,6 +32,10 @@ const navConfig = [ title: 'Alarm Center', path: '/alarm-center', }, + { + title: 'Formularium', + path: '/master/formularium-template-v2', + } // { // title: 'Claim Submit', // path: '/claim-submit', diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdate.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdate.tsx new file mode 100644 index 00000000..2e1aa7f7 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdate.tsx @@ -0,0 +1,69 @@ +import { useNavigate, useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import {useContext, useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { useSnackbar } from 'notistack'; +import { ConfiguredCorporateContext } from "@/contexts/ConfiguredCorporateContext"; +import { Corporate, CorporatePlan } from "@/@types/corporates"; +import { MasterFormularium } from "./Type"; +import CreateUpdateForm from "./CreateUpdateForm"; +import Typography from "@/theme/overrides/Typography"; + +export default function CreateUpdate() { + const navigate = useNavigate(); + const { corporate_id, id } = useParams(); + const [corporate, setCorporate] = useState(); + const configuredCorporateContext = useContext(ConfiguredCorporateContext) + + useEffect(() => { + setCorporate(configuredCorporateContext.currentCorporate); + }, [ConfiguredCorporateContext]) + + const [ currentCorporatePlan, setCurrentCorporatePlan ] = useState(); + const [ currentMasterForm, setCurrentMasterForm ] = useState(); + const isEdit = !!id; + + useEffect(() => { + if (isEdit) { + axios.get(`/master/formularium-template/${id}/edit`) + .then((res) => { + // setCurrentCorporatePlan(res.data); + setCurrentMasterForm(res.data); + }) + .catch((err) => { + if (err.response.status === 404) { + navigate('/404'); + } + }) + } + }, [corporate_id, id]) + + const pageType = !isEdit ? "Create" : "Edit" + const pageTitle = `Master Formularium ${pageType}` + return ( + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdateForm.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdateForm.tsx new file mode 100644 index 00000000..b93d3bfd --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdateForm.tsx @@ -0,0 +1,143 @@ +import * as Yup from 'yup'; +import { LoadingButton } from "@mui/lab"; +import { Button, Card, Grid, Stack, Typography } from "@mui/material"; +import { CorporatePlan } from "../../../@types/corporates"; +import { FormProvider, RHFSwitch, RHFTextField } from "../../../components/hook-form"; +import { useEffect, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate, useParams } from 'react-router-dom'; +import axios from '../../../utils/axios'; +import { MasterFormularium } from "./Type"; + + +type Props = { + isEdit: boolean; + currentMasterForm?: MasterFormularium; +}; + +export default function CreateUpdateForm({ isEdit, currentMasterForm }: Props) { + const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporatePlanSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + }); + + const defaultValues = useMemo( + () => ({ + name: currentMasterForm?.name || '', + description: currentMasterForm?.description || '', + }), + [currentMasterForm] + ) + + useEffect(() => { + if (isEdit && currentMasterForm) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + }, [isEdit, currentMasterForm]) + + const methods = useForm({ + resolver: yupResolver(NewCorporatePlanSchema), + defaultValues, + }) + + const { + reset, + setError, + handleSubmit, + formState: { isSubmitting } + } = methods; + + const onSubmit = async (data: any) => { + if (!isEdit) { + await axios + .post('/master/formularium-template/store', data) + .then((res) => { + enqueueSnackbar('Formularium created succesfully', {variant: 'success'}); + }) + .then((res) => { + navigate('/master/formularium-template-v2', { replace: true }); + }) + .catch(({ response }) => { + if (response.status === 422) { + for (const [key, value] of Object.entries(response.data.errors)) { + setError(key, { message: value[0] }); + enqueueSnackbar(value[0] ?? 'Failed Processing Reques', { variant: 'error' }); + } + } + else { + enqueueSnackbar('Create Failed : ' + response.data.message, {variant: 'error'}); + } + }); + } else { + await axios + .put(`/master/formularium-template/${currentMasterForm?.id}/update`, data) + .then((res) => { + enqueueSnackbar('Formularium updated successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/master/formularium-template-v2', { replace: true }); + }) + .catch(({ response }) => { + enqueueSnackbar(`Update Failed : ${response.data.message}`, { variant: 'error' }) + }) + } + } + + return ( + + + + + + Detail + Name* + + Description* + + + + + + + + { isEdit? 'Update' : 'Create' } + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/DetailFormularium.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/DetailFormularium.tsx new file mode 100644 index 00000000..049b3c3a --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/DetailFormularium.tsx @@ -0,0 +1,102 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Grid, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import { LoadingButton } from "@mui/lab"; +import { CachedOutlined, FindInPageOutlined } from '@mui/icons-material'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../../hooks/useSettings'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; +import { RHFSelect, FormProvider } from '@/components/hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useForm } from 'react-hook-form'; +// components +import axios from '../../../../utils/axios'; +import Label from '@/components/Label'; +import TableMoreMenu from '@/components/table/TableMoreMenu'; +import DialogUpdateStatus from '@/components/DialogUpdateStatus' +import * as Yup from 'yup'; +import { FormulariumData } from "../Type"; + + +type Props = { + props: FormulariumData, + isActive: number, +} + +export default function DetailFormularium({props, isActive} : Props) { + const [open, setOpen] = React.useState(false); + + + return ( + + + + {props.code} + {props.atc_code} + {props.name} + {props.category_name} + {props.uom} + + {isActive == 1 ? ( + + ) : ( + + )} + + + + setOpen(!open)}> + + Detail + + + } /> + + + + + + + + + + Detail + + Description + : {props.description} + + General Indication + : {props.general_indication} + + Composition + : {props.composition} + + Kategori Obat + : {props.kategori_obat} + + BPOM Registration + : {props.bpom_registration} + + Classification + : {props.classifications} + + Cat For + : {props.cat_for} + + Class + : {props.class} + + Manufacturer + : {props.manufacturer} + + + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Formularium.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Formularium.tsx new file mode 100644 index 00000000..13e09315 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Formularium.tsx @@ -0,0 +1,316 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Grid, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../../hooks/useSettings'; +import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../../../utils/axios'; +import { LaravelPaginatedData } from '../../../../@types/paginated-data'; +import { Icd } from '../../../../@types/diagnosis'; +import BasePagination from '../../../../components/BasePagination'; +import { enqueueSnackbar } from 'notistack'; +import DetailFormularium from "./DetailFormularium"; +import { FormulariumData } from '../Type'; + + +export default function List() { + const navigate = useNavigate(); + const { id: formularium_template_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + const { isActive } = useLocation().state as { isActive: number } + + function SearchInput(props: any) { + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); + } + + useEffect(() => { + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ); + } + + function ImportForm( props: any ) { + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null) + const [currentImportFileName, setCurrentImportFileName] = useState(null) + + 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 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 handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name) + } else { + setCurrentImportFileName(null); + } + } + + const handleFormulariumList = async (appliedFilter = null) => { + axios.get(`master/formulariums/${formularium_template_id}/list`) + .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(); + enqueueSnackbar('Download Success', { variant: 'success' }) + }) + .catch(response => { + enqueueSnackbar('Looks like something went wrong. Please check your data and try again. ' + response.message, { variant: 'error' }) + }); + } + + const handleUpload = () => { + if (importForm.current?.files?.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]) + axios.post(`master/formulariums/${formularium_template_id}/import`, formData ) + .then(response => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + }) + .catch(response => { + enqueueSnackbar('Looks like something went wrong. Please check your data and try again. ' + response.message, { variant: 'error' }) + }) + } else { + enqueueSnackbar(`No File Selected`, { variant: 'warning' }) + } + } + + return ( +
+ + {(!currentImportFileName && + + + + Import + handleGetTemplate('master-formularium')}>Download Template + handleFormulariumList()}>Download Formularium + + + )} + {( currentImportFileName && + + + + + + + )} + {( importResult && + + Last Import Result Report : {importResult.result_file?.name ?? "-"} + + )} +
+ ); + } + + // Default data + const [dataTableRow, setDataTableRow] = useState(null) + const [dataTableIsLoading, setDataTableLoading] = useState(true); + 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 loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get(`/master/formulariums/${formularium_template_id}`, { params: filter }); + setDataTableLoading(false); + + console.log(formularium_template_id) + console.log(response) + setDataTableData(response.data) + setDataTableRow(response.data.data) + } + + const applyFilter = async (searchFilter: string) => { + await loadDataTableData({ 'search': searchFilter }); + setSearchParams({ 'search' : searchFilter }); + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + useEffect(() => { + loadDataTableData(); + }, []) + + const headStyle = { + fontWeight: 'bold', + }; + + return ( + + + + + + + + + Code + ATC Code + Name + Category Name + UOM + Status + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + dataTableRow?.map(item => ( + + )) + )} +
+
+ + +
+
+ ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Index.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Index.tsx new file mode 100644 index 00000000..381283ef --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Index.tsx @@ -0,0 +1,37 @@ +import { Card, Grid } 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 "./Formularium"; + + +export default function Formularium() { + const pageTitle = "Formularium" + const { id: formularium_template_id } = useParams(); + + return ( + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/FormulariumRow.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/FormulariumRow.tsx new file mode 100644 index 00000000..47c90524 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/FormulariumRow.tsx @@ -0,0 +1,211 @@ +import * as Yup from 'yup'; +import TableMoreMenu from "@/components/table/TableMoreMenu" +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined'; +import HistoryIcon from '@mui/icons-material/History'; +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; +import { Button, Card, Collapse, Grid, MenuItem, Paper, Stack, Table, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material" +import React, { Fragment, useEffect, useState } from "react"; +import axios from "@/utils/axios"; +import { useNavigate, useParams } from "react-router-dom"; +import { RHFSelect, FormProvider } from '@/components/hook-form'; +import DialogUpdateStatus from '@/components/DialogUpdateStatus' +import { MasterFormularium } from "./Type"; +import { LoadingButton } from "@mui/lab"; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; + +type Props = { + props: MasterFormularium +} + +export default function FormulariumRow({props} : Props) { + const navigate = useNavigate(); + + const [isDialogOpen, setDialogOpen] = useState(false); + const [dataValue, setDataValue] = useState(null); + const [dataDescription, setDescriptionValue] = useState(''); + const [url, setUrl] = useState(''); + let titles = { + name: 'Update Status', + icon: '-' + } + type FormValuesProps = { + value: string; + active: boolean; + }; + + const handleActivate = (isOpen: boolean, dataValue: MasterFormularium) => { + setDialogOpen(isOpen); + setDataValue(dataValue); + setDescriptionValue("Are you sure to inactive this formularium ? "); + setUrl(url); + }; + + const NewMasterFormSchema = Yup.object().shape({ + reason: Yup.string().required('Reason edit is required') + }); + + const methods = useForm({ + resolver: yupResolver(NewMasterFormSchema) + }); + + const onSubmit = async (row: any) => { + try { + handleUpdate(dataValue?.active, dataValue?.id, row.reason) + } catch (error: any) { + console.log('data gagal'); + } + + const ascent = document?.querySelector('ascent'); + if (ascent != null) { + ascent.innerHTML = ''; + } + }; + + const handleUpdate = (active: number, id: number, reason: string) => { + + if (active == 1) { + active = 0 + } else { + active = 1 + } + + axios + .put(`/master/formularium-template/${id}/activation`, { + active: active, + }) + .then((res) => { + window.location.reload(); + }) + } + + const { + reset, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const getContent = () => ( + <> + + Are you sure to Change this Formularium + + + + + Formularium name + + + {dataValue?.name} + + + + + Reason for update + + + + + + + + + + + + {dataValue?.active == 1 ? + + Inactive + : + + Active + + } + + + + + + + ) + + return ( + + + + {props.name} + {props.description} + + + navigate(`/master/formularium-template-v2/${props.id}/detail`, {state: { isActive: props.active }})}> + + Detail + + navigate(`/master/formularium-template-v2/${props.id}/edit`)}> + + Edit + + handleActivate(true, { + id: props.id, + name: props.name, + description: props.description, + active: props.active + }) + }> + + Update Status + + navigate(`/master/formularium-template-v2/${props.id}/history`)}> + + History + + + } /> + + + + + ) +} diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/History.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/History.tsx new file mode 100644 index 00000000..ca991be6 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/History.tsx @@ -0,0 +1,211 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + Container, + FormControl, + Grid, + IconButton, + InputLabel, + MenuItem, + OutlinedInput, + Paper, + Select, + SelectChangeEvent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Badge, + Stack, + } from '@mui/material'; + import * as React from 'react'; +import { useParams } from 'react-router-dom'; +import { styled } from '@mui/material/styles'; +import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'; +import MuiAccordion, { AccordionProps } from '@mui/material/Accordion'; +import { useContext, useEffect, useState } from 'react'; +import MuiAccordionSummary, { + AccordionSummaryProps, +} from '@mui/material/AccordionSummary'; +import useSettings from '../../../hooks/useSettings'; +import axios from '../../../utils/axios'; +import { ConfiguredCorporateContext } from '@/contexts/ConfiguredCorporateContext'; +import MuiAccordionDetails from '@mui/material/AccordionDetails'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import { Corporate } from '@/@types/corporates'; +import { fDate, fDateTime } from '@/utils/formatTime'; + + +const Accordion = styled((props: AccordionProps) => ( + +))(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + "&:not(:last-child)": { + borderBottom: 0, + }, + "&:before": { + display: 'none' + }, +})); + +const AccordionSummary = styled((props: AccordionSummaryProps) => ( + } + {...props} + /> +))(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' + ? 'rgba(255,255,255,0.5)' + : 'rgba(0,0,0,.03)', + flexDirection: 'row-reverse', + "& .MuiAccordionSummary-expandIconWrapper.Mui-expanded": { + transform: 'rotate(90dg)', + }, + "& .MuiAccordionSummary=content": { + marginLeft: theme.spacing(1), + }, +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + padding: theme.spacing(2), + borderTop: '1px solid rgba(0,0,0,.125)', +})); + +export default function MasterFormulariumHistory() { + const [expanded, setExpanded] = React.useState('panel1'); + + const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { + setExpanded(newExpanded ? panel : false); + }; + const pageTitle = 'Master Formularium History' + const { id } = useParams(); + const { corporate_id } = useParams(); + const [corporate, setCorporate] = useState(); + const [currentCorporate, setCurrentCorporate] = useState(); + const configuredCorporateContext = useContext(ConfiguredCorporateContext); + + useEffect(() => { + setCorporate(configuredCorporateContext.currentCorporate); + const model = 'App\\Models\\FormulariumTemplate'; + const url = `/audittrail/${id}?model=${model}`; + axios.get(url) + .then((res) => { + setCurrentCorporate(res.data); + }) + .catch((error) => { + console.error('Terjadi kesalahan: ', error); + }); + }, [configuredCorporateContext]) + + return ( +
+ + {currentCorporate?.data.map((item, index) => ( + + + {`Data has ${item.action} by ${item.user_id} on ${fDateTime(item.updated_at)}`} + + + + + + + Field + Old Value + New Values + + + + {Object.entries(item.old_values).map(([key, value]) => { + let renderedValue; + if (key === 'deleted_by' || + key === 'deleted_at' || + key === 'created_by' || + key === 'created_at' || + key === 'updated_by' || + key === 'description' + ) { + return null; // Melewati iterasi saat key adalah 'deleted_by' + } + switch (key) { + case 'welcome_message': + renderedValue = item.new_values[key].replace(/<[^>]*>/g, ''); + value = value.replace(/<[^>]*>/g, ''); + break; + case 'help_text': + renderedValue = item.new_values[key].replace(/<[^>]*>/g, ''); + value = value.replace(/<[^>]*>/g, ''); + break; + case 'active': + renderedValue = item.new_values[key] == 1 ? 'Active' : 'Inactive'; + value = value == 1 ? 'Active' : 'Inactive'; + break; + case 'created_at': + renderedValue = fDateTime(item.new_values[key]); + value = fDateTime(value); + break; + case 'updated_at': + renderedValue = fDateTime(item.new_values[key]); + value = fDateTime(value); + break; + case 'delete_at': + renderedValue = fDateTime(item.new_values[key]); + value = fDateTime(value); + break; + default: + renderedValue = item.new_values[key]; + break; + } + + const field = key.charAt(0).toUpperCase() + key.slice(1); + if (value == renderedValue) { + return null + } else { + return ( + + {`${field}`} + {`${value}`} + {renderedValue} + + ); + } + })} + +
+
+
+
+ ))} +
+ ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Index.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Index.tsx new file mode 100644 index 00000000..2a25563b --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Index.tsx @@ -0,0 +1,33 @@ +import { Card, Grid } 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 MasterFormularium() { + const pageTitle = "Master Formularium" + + return ( + + + + + + + + + ) +} diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/List.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/List.tsx new file mode 100644 index 00000000..25dae159 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/List.tsx @@ -0,0 +1,179 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Grid, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +// hooks +import React, { ChangeEvent, Component, useEffect, useContext, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../../utils/axios'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import BasePagination from '../../../components/BasePagination'; +import FormulariumRow from "./FormulariumRow"; +import { MasterFormularium } from "./Type"; +import { UserCurrentCorporateContext } from '../../../contexts/UserCurrentCorporate'; + +export default function List() { + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + + // Default data + const [dataTableRow, setDataTableRow] = useState(null) + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const { corporateValue } = useContext(UserCurrentCorporateContext); + 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 loadDataTableData =async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + + const filter = { + ...Object.fromEntries([...searchParams.entries()]), // Mengambil parameter dari searchParams + corporate_id: corporateValue, // Menambahkan corporate_id + ...(appliedFilter ? appliedFilter : {}) // Menggabungkan dengan appliedFilter jika ada + }; + const response = await axios.get('/master/formularium-template', { params: filter }); + console.log(response.data); + console.log(response.data.data) + setDataTableLoading(false); + + setDataTableData(response.data); + setDataTableRow(response.data.data); + } + + const applyFilter = async (searchFilter: string) => { + await loadDataTableData({'search' : searchFilter }); + setSearchParams({ 'search' : searchFilter }) + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + useEffect(() => { + loadDataTableData(); + }, []) + + function SearchInput(props: any) { + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? "" + setSearchText(newSearchText) + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); + } + + useEffect(() => { + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ) + } + + function SearchCreate(props: any) { + return ( +
+ + + {/* */} + +
+ ) + } + + const headStyle = { + fontWeight: 'bold' + } + + return ( + + + + + + + + + + Name + Description + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableRow?.map(item => ( + + ))} + + )} +
+
+ +
+
+
+ ) +} diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Type.ts b/frontend/client-portal/src/pages/Master/FormulariumV2/Type.ts new file mode 100644 index 00000000..3e16e32e --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Type.ts @@ -0,0 +1,32 @@ +export type MasterFormularium = { + id: number + name: string + description: string + active: number +} + + +export type FormulariumData = { + id: number + code: string + name: string + description: string + manufacturer: string + category_name: string + kategori_obat: string + uom: string + general_indication: string + composition: string + atc_code: string + class: string + bpom_registration: string + classifications: string + cat_for: string + created_at: string + updated_at: string + deleted_at: any + created_by: number + updated_by: number + deleted_by: any + formularium_template_id: number + } \ No newline at end of file diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index a43eba44..4bf282dd 100644 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -234,6 +234,86 @@ export default function Router() { { path: '*', element: }, ], }, + { + path: 'master/formularium-template-v2', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/:id/detail', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/create', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/:id/edit', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/:id/history', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, { path: '*', element: }, ]); } @@ -275,3 +355,9 @@ const ClaimRequest = Loadable(lazy(() => import('../pages/ClaimSubmit/DialogDeta const Corporate = Loadable(lazy(() => import('../pages/Corporate/Index'))); const CorporateEdit = Loadable(lazy(() => import('../pages/Corporate/Form'))); const CorporateShow = Loadable(lazy(() => import('../pages/Corporate/Show'))); + +// Formularium +const MasterFormulariumTemplateV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/Index'))); +const MasterFormulariumTemplateCreateV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/CreateUpdate'))); +const MasterFormulariumTemplateHistoriesV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/History'))); +const MasterFormulariumTemplateDetailV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/Detail/Index')));