diff --git a/Modules/Client/Http/Controllers/Api/AuthController.php b/Modules/Client/Http/Controllers/Api/AuthController.php index 26eaee11..b0596e59 100755 --- a/Modules/Client/Http/Controllers/Api/AuthController.php +++ b/Modules/Client/Http/Controllers/Api/AuthController.php @@ -15,39 +15,39 @@ class AuthController extends Controller public function requestOtp(Request $request) { $request->validate([ - 'phone_or_email' => 'required' + 'phoneOrEmail' => 'required' ]); $user = User::query() - ->when(filter_var($request->phone_or_email, FILTER_VALIDATE_EMAIL), function (Builder $query) use ($request) { - $query->getQuery()->where('email', $request->phone_or_email); + ->when(filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL), function (Builder $query) use ($request) { + $query->getQuery()->where('email', $request->phoneOrEmail); }, function (Builder $query) use ($request) { - $query->getQuery()->where('phone', $request->phone_or_email); + $query->getQuery()->where('phone', $request->phoneOrEmail); }) ->first(); if (!$user) { - $message = filter_var($request->phone_or_email, FILTER_VALIDATE_EMAIL) ? "User dengan alamat email " . $request->phone_or_email . " tidak ditemukan" : "User dengan nomor telepon " . $request->phone_or_email . " tidak ditemukan"; + $message = filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL) ? "User dengan alamat email " . $request->phoneOrEmail . " tidak ditemukan" : "User dengan nomor telepon " . $request->phoneOrEmail . " tidak ditemukan"; return Helper::responseJson(statusCode: Response::HTTP_NOT_FOUND, message: $message); } - if (filter_var($request->phone_or_email, FILTER_VALIDATE_EMAIL)) { + if (filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL)) { User::query()->find($user->id)->update([ - 'email' => $request->phone_or_email, + 'email' => $request->phoneOrEmail, 'otp' => rand(1000, 9999), 'otp_created_at' => now() ]); } else { User::query()->find($user->id)->update([ - 'phone' => $request->phone_or_email, + 'phone' => $request->phoneOrEmail, 'otp' => rand(1000, 9999), 'otp_created_at' => now() ]); } // TODO Send the OTP - if (filter_var($request->phone_or_email, FILTER_VALIDATE_EMAIL)) { + if (filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL)) { // Send Email } else { // Send Whatsapp @@ -59,15 +59,15 @@ class AuthController extends Controller public function validateOtp(Request $request) { $request->validate([ - 'phone_or_email' => 'required', + 'phoneOrEmail' => 'required', 'otp' => 'required' ]); $user = User::query() - ->when(filter_var($request->phone_or_email, FILTER_VALIDATE_EMAIL), function (Builder $query) use ($request) { - $query->getQuery()->where('email', $request->phone_or_email); + ->when(filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL), function (Builder $query) use ($request) { + $query->getQuery()->where('email', $request->phoneOrEmail); }, function (Builder $query) use ($request) { - $query->getQuery()->where('phone', $request->phone_or_email); + $query->getQuery()->where('phone', $request->phoneOrEmail); }) ->first(); @@ -86,9 +86,8 @@ class AuthController extends Controller public function logout(Request $request) { - $token = $request->bearerToken(); - Auth::user()->tokens()->where('id', $token)->delete(); + $request->user()->currentAccessToken()->delete(); - return response(['message' => 'Berhasil Logout.']); + return Helper::responseJson(message: 'Berhasil Logout.'); } } diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index b9dc6f98..2ab4c9af 100755 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -19,8 +19,8 @@ use Modules\Client\Http\Controllers\Api\UserController; Route::prefix('client')->group(function () { Route::controller(AuthController::class)->group(function () { - Route::post('otp-request', 'requestOtp'); - Route::post('otp-validation', 'validateOtp'); + Route::post('login', 'login'); + Route::post('verify-code', 'validateOtp'); }); Route::middleware('auth:sanctum')->group(function () { diff --git a/frontend/client-portal/src/@types/auth.ts b/frontend/client-portal/src/@types/auth.ts index 9d25638d..dbc15390 100755 --- a/frontend/client-portal/src/@types/auth.ts +++ b/frontend/client-portal/src/@types/auth.ts @@ -1,4 +1,4 @@ -import { UserCredential } from 'firebase/auth'; +// import { UserCredential } from 'firebase/auth'; // ---------------------------------------------------------------------- @@ -26,19 +26,20 @@ export type JWTContextType = { isInitialized: boolean; user: AuthUser; method: 'jwt'; - // login: (phone_or_email: string) => Promise; + login: (phoneOrEmail: string) => Promise; + validateOtp: (phoneOrEmail: string, otp: string) => Promise logout: () => Promise; }; -export type FirebaseContextType = { - isAuthenticated: boolean; - isInitialized: boolean; - user: AuthUser; - method: 'firebase'; - login: (email: string, password: string) => Promise; - register: (email: string, password: string, firstName: string, lastName: string) => Promise; - logout: () => Promise; -}; +// export type FirebaseContextType = { +// isAuthenticated: boolean; +// isInitialized: boolean; +// user: AuthUser; +// method: 'firebase'; +// login: (email: string, password: string) => Promise; +// register: (email: string, password: string, firstName: string, lastName: string) => Promise; +// logout: () => Promise; +// }; export type AWSCognitoContextType = { isAuthenticated: boolean; diff --git a/frontend/client-portal/src/App.tsx b/frontend/client-portal/src/App.tsx index 449aed87..8ff04cf4 100755 --- a/frontend/client-portal/src/App.tsx +++ b/frontend/client-portal/src/App.tsx @@ -3,28 +3,33 @@ import Router from './routes'; // theme import ThemeProvider from './theme'; // components -import Settings from './components/settings'; import RtlLayout from './components/RtlLayout'; import ScrollToTop from './components/ScrollToTop'; import { ProgressBarStyle } from './components/ProgressBar'; import ThemeColorPresets from './components/ThemeColorPresets'; import MotionLazyContainer from './components/animate/MotionLazyContainer'; +import { SnackbarProvider } from 'notistack'; // ---------------------------------------------------------------------- export default function App() { return ( - - - - - {/* */} - - - - - + + + + + + {/* */} + + + + + + ); } diff --git a/frontend/client-portal/src/components/MuiDialog.tsx b/frontend/client-portal/src/components/MuiDialog.tsx new file mode 100644 index 00000000..c445a216 --- /dev/null +++ b/frontend/client-portal/src/components/MuiDialog.tsx @@ -0,0 +1,56 @@ +import { Dialog, DialogTitle, DialogContent, Stack, Typography, IconButton } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { ReactElement } from 'react'; +import Iconify from './Iconify'; + +// ---------------------------------------------------------------------- + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + maxWidth?: string; +}; + +// ---------------------------------------------------------------------- + +const MuiDialog = ({ title, openDialog, setOpenDialog, content, maxWidth }: MuiDialogProps) => { + const handleClose = () => { + setOpenDialog(false); + }; + + let maxWidthDialog = 'md'; + + if (maxWidth) { + maxWidthDialog = maxWidth; + } + + return ( + + + + {title?.icon ? ( + + + {title?.name} + + ) : ( + {title?.name ? title?.name : 'Testing Title'} + )} + + + + + + + {content ? content : 'Testing Content Dialog'} + + + ); +}; + +export default MuiDialog; diff --git a/frontend/client-portal/src/components/nav-section/type.ts b/frontend/client-portal/src/components/nav-section/type.ts index 870407d8..f8a9ea44 100755 --- a/frontend/client-portal/src/components/nav-section/type.ts +++ b/frontend/client-portal/src/components/nav-section/type.ts @@ -29,7 +29,7 @@ export type NavItemProps = { export interface NavSectionProps extends BoxProps { isCollapse?: boolean; navConfig: { - subheader: string; + subheader?: string; items: NavListProps[]; }[]; } diff --git a/frontend/client-portal/src/components/nav-section/vertical/NavItem.tsx b/frontend/client-portal/src/components/nav-section/vertical/NavItem.tsx index cc6ca677..44826f7b 100755 --- a/frontend/client-portal/src/components/nav-section/vertical/NavItem.tsx +++ b/frontend/client-portal/src/components/nav-section/vertical/NavItem.tsx @@ -10,12 +10,18 @@ import { isExternalLink } from '..'; // ---------------------------------------------------------------------- -export function NavItemRoot({ item, isCollapse, open = false, active, onOpen }: NavItemProps) { - const { title, path, icon, info, children } = item; +export function NavItemRoot({ + item, + isCollapse, + open = false, + active = false, + onOpen, +}: NavItemProps) { + const { title, path, info, children } = item; const renderContent = ( <> - {icon && {icon}} + {!isCollapse && ( <> diff --git a/frontend/client-portal/src/contexts/LaravelAuthContext.tsx b/frontend/client-portal/src/contexts/LaravelAuthContext.tsx index d78a6afc..eed11130 100755 --- a/frontend/client-portal/src/contexts/LaravelAuthContext.tsx +++ b/frontend/client-portal/src/contexts/LaravelAuthContext.tsx @@ -1,18 +1,17 @@ import { createContext, ReactNode, useEffect, useReducer } from 'react'; // utils import axios from '../utils/axios'; -// import { isValidToken, setSession } from '../utils/jwt'; -import { setSession, getSession, getUser } from '../utils/token'; +import { setSession, getSession } from '../utils/token'; // @types import { ActionMap, AuthState, AuthUser, JWTContextType } from '../@types/auth'; + // ---------------------------------------------------------------------- -// import { Navigate, useLocation } from 'react-router-dom'; enum Types { Initial = 'INITIALIZE', Login = 'LOGIN', + ValidateOtp = 'VALIDATE-OTP', Logout = 'LOGOUT', - Register = 'REGISTER', } type JWTAuthPayload = { @@ -20,13 +19,11 @@ type JWTAuthPayload = { isAuthenticated: boolean; user: AuthUser; }; - [Types.Login]: { + [Types.Login]: undefined; + [Types.ValidateOtp]: { user: AuthUser; }; [Types.Logout]: undefined; - [Types.Register]: { - user: AuthUser; - }; }; export type JWTActions = ActionMap[keyof ActionMap]; @@ -46,6 +43,12 @@ const JWTReducer = (state: AuthState, action: JWTActions) => { user: action.payload.user, }; case 'LOGIN': + return { + ...state, + isAuthenticated: false, + user: null, + }; + case 'VALIDATE-OTP': return { ...state, isAuthenticated: true, @@ -57,14 +60,6 @@ const JWTReducer = (state: AuthState, action: JWTActions) => { isAuthenticated: false, user: null, }; - - case 'REGISTER': - return { - ...state, - isAuthenticated: true, - user: action.payload.user, - }; - default: return state; } @@ -80,11 +75,11 @@ type AuthProviderProps = { function AuthProvider({ children }: AuthProviderProps) { const [state, dispatch] = useReducer(JWTReducer, initialState); - // let location = useLocation(); useEffect(() => { - const initialize = async () => { + (async () => { console.log('initialize', state); + try { const accessToken = getSession(); @@ -120,51 +115,43 @@ function AuthProvider({ children }: AuthProviderProps) { }, }); } - }; - - initialize(); + })(); }, []); - // const login = async (phone_or_email: string) => { - // axios - // .post('/otp-request', { phone_or_email }) - // .then((response: any) => { - // const { user, token } = response.data; - // setSession(token); + const login = async (phoneOrEmail: string) => + axios + .post('/login', { phoneOrEmail }) + .then(() => { + dispatch({ + type: Types.Login, + }); + }) + .catch((error) => { + if (error.response.status !== 404) throw error.response; + if (error.response.status !== 422) throw error.response; + }); - // dispatch({ - // type: Types.Login, - // payload: { - // user, - // }, - // }); - // }) - // .catch((error) => { - // if (error.response.status !== 404) throw error.response; - // if (error.response.status !== 422) throw error.response; - // }); - // }; + const validateOtp = async (phoneOrEmail: string, otp: string) => + axios + .post('/verify-code', { phoneOrEmail: phoneOrEmail, otp }) + .then((response) => { + const { user, token } = response.data.data; + setSession(token); - // const register = async (email: string, password: string, firstName: string, lastName: string) => { - // const response = await axios.post('/api/register', { - // email, - // password, - // firstName, - // lastName, - // }); - // const { accessToken, user } = response.data; - - // window.localStorage.setItem('accessToken', accessToken); - // dispatch({ - // type: Types.Register, - // payload: { - // user, - // }, - // }); - // }; + dispatch({ + type: Types.ValidateOtp, + payload: { + user, + }, + }); + }) + .catch((error) => { + if (error.response.status !== 404) throw error.response; + if (error.response.status !== 422) throw error.response; + }); const logout = async () => { - await axios.post('/logout', { token: getSession() }); + await axios.post('/logout'); setSession(null); dispatch({ type: Types.Logout }); }; @@ -174,6 +161,8 @@ function AuthProvider({ children }: AuthProviderProps) { value={{ ...state, method: 'jwt', + login, + validateOtp, logout, }} > diff --git a/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx index 236a6307..4859a683 100755 --- a/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx @@ -1,56 +1,24 @@ -// components -import SvgIconStyle from '../../../components/SvgIconStyle'; - // ---------------------------------------------------------------------- -const getIcon = (name: string) => ( - -); - -const ICONS = { - user: getIcon('ic_user'), - ecommerce: getIcon('ic_ecommerce'), - analytics: getIcon('ic_analytics'), - dashboard: getIcon('ic_dashboard'), -}; - const navConfig = [ // GENERAL // ---------------------------------------------------------------------- { items: [{ title: 'Dashboard', path: '/dashboard' }], }, - - // Case Management + // Alarm Center // ---------------------------------------------------------------------- { subheader: 'Case Management', items: [ { title: 'Alarm Center', - path: '/members', - // icon: ICONS.default, + path: '/alarm-center', }, { title: 'Claim Report', - path: '/claim-reports', - // icon: ICONS.default, + path: '/claim-report', }, - // { - // title: 'Member List', - // path: '/members', - // icon: ICONS.user, - // }, - // { - // title: 'Member Movement', - // // path: '/', - // icon: ICONS.user, - // children: [ - // { title: '', path: '/medicines' }, - // { title: 'Obat', path: '/medicines' }, - // { title: 'Obat', path: '/medicines' }, - // ], - // }, ], }, diff --git a/frontend/client-portal/src/pages/AlarmCenter/Index.tsx b/frontend/client-portal/src/pages/AlarmCenter/Index.tsx new file mode 100755 index 00000000..1f00f75f --- /dev/null +++ b/frontend/client-portal/src/pages/AlarmCenter/Index.tsx @@ -0,0 +1,31 @@ +// mui +import { Container, Grid, Card } from '@mui/material'; +// components +import Page from '../../components/Page'; +// utils +import useSettings from '../../hooks/useSettings'; +// sections +// import ListTable from '../../sections/claimreports/ListTable'; +// import ClaimStatusCard from '../../sections/claimreports/ClaimStatusCard'; + +import List from './List'; + +export default function Drugs() { + const { themeStretch } = useSettings(); + + // const { corporate_id } = useParams(); + + return ( + + + + + + + + + + + + ); +} diff --git a/frontend/client-portal/src/pages/AlarmCenter/List.tsx b/frontend/client-portal/src/pages/AlarmCenter/List.tsx new file mode 100755 index 00000000..5b362fcd --- /dev/null +++ b/frontend/client-portal/src/pages/AlarmCenter/List.tsx @@ -0,0 +1,417 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + IconButton, + InputLabel, + MenuItem, + OutlinedInput, + Paper, + Select, + SelectChangeEvent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Badge, + Tab, + Tabs, + CardHeader, + Stack, + Menu, + ButtonGroup, + Pagination, + Grid, +} from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../hooks/useSettings'; +import { 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 { Member } from '../../@types/member'; + +export default function List() { + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const { corporate_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + function SearchInput(props: any) { + // SEARCH + 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); // Trigger to Parent + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]); + + return ( +
+ + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleImportButton = () => { + if (importForm?.current) { + handleClose(); + importForm.current ? importForm.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + + const handleCancelImportButton = () => { + importForm.current.value = ''; + importForm.current.dispatchEvent(new Event('change', { bubbles: true })); + }; + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]); + axios + .post(`master/formularium/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + }) + .catch((response) => { + alert( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message + ); + }); + } else { + alert('No File Selected'); + } + }; + + return ( +
+ + {!currentImportFileName && ( + + + + )} + + {currentImportFileName && ( + + + + + + + + + )} + {importResult && ( + + + Last Import Result Report :{' '} + + {importResult.result_file?.name ?? '-'} + + + + )} +
+ ); + } + + // Called on every row to map the data to the columns + function createData(member: Member): Member { + return { + ...member, + }; + } + + // Generate the every row of the table + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(true); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)}> + {open ? : } + + + {row.member_id} + {row.payor_id} + {row.name} + {row.nik} + {row.nric} + + + + + {/* */} + + {/* COLLAPSIBLE ROW */} + + + + + + + + + + + + + ); + } + + // 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('/members', { params: filter }); + + setDataTableData(response.data.members); + setDataTableLoading(false); + }; + + const headStyle = { + fontWeight: 'bold', + }; + + 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(); + }, []); + + return ( + + + + + + + + + + + {/* The Main Table */} + + + + + + No + + + Name + + + Member ID + + + Service + + + Start Date + + + End Date + + + Status + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} +
+
+ + +
+
+ ); +} diff --git a/frontend/client-portal/src/pages/AlarmCenter/ServiceMonitoring.tsx b/frontend/client-portal/src/pages/AlarmCenter/ServiceMonitoring.tsx new file mode 100755 index 00000000..add01b70 --- /dev/null +++ b/frontend/client-portal/src/pages/AlarmCenter/ServiceMonitoring.tsx @@ -0,0 +1,291 @@ +// mui +import { + Button, + Box, + Tabs, + Tab, + IconButton, + Container, + Grid, + Card, + Stack, + Typography, +} from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { Favorite } from '@mui/icons-material'; +// components +import Page from '../../components/Page'; +import Iconify from '../../components/Iconify'; +// utils +import useSettings from '../../hooks/useSettings'; +import { useRef, useState, SyntheticEvent } from 'react'; +// sections +// import ListTable from '../../sections/claimreports/ListTable'; +// import ClaimStatusCard from '../../sections/claimreports/ClaimStatusCard'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +function a11yProps(index: number) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}`, + }; +} + +interface StyledTabsProps { + children?: React.ReactNode; + value: number; + onChange: (event: React.SyntheticEvent, newValue: number) => void; +} + +const StyledTabs = styled((props: StyledTabsProps) => )({ + '& .MuiTabs-indicator': { + display: 'flex', + justifyContent: 'center', + backgroundColor: 'transparent', + }, + '& .MuiTabs-indicatorSpan': { + maxWidth: 40, + width: '100%', + backgroundColor: '#635ee7', + }, +}); + +interface StyledTabProps { + label: string; + icon?: string | React.ReactElement; +} + +const StyledTab = styled((props: StyledTabProps) => )( + ({ theme }) => ({ + textTransform: 'none', + fontWeight: 500, + fontSize: theme.typography.pxToRem(20), + color: theme.palette.primary.main, + maxWidth: '100%', + flex: 1, + margin: '0 !important', + '&.Mui-selected': { + color: '#FFF', + backgroundColor: theme.palette.primary.main, + }, + '&:hover': { + backgroundColor: theme.palette.primary.dark, + color: '#FFF', + opacity: 1, + }, + }) +); + +export default function Drugs() { + const { themeStretch } = useSettings(); + + const [value, setValue] = useState(0); + const handleChange = (event: SyntheticEvent, newValue: number) => { + setValue(newValue); + }; + + return ( + + + + + + + Service Monitoring + + + Done + + + + {/* Item 1 */} + + + + + + Employee Profiles + + + + + Nama perusahaan + PT. Amman Mineral + + + Nama Lengkap + Stephen kuow + + + Tanggal lahir + 09 Aug 1980 + + + Email + Stephen.uow@gmal.com + + + No telepon + +62 821-8123-2323 + + + + ID Karyawan + 12345678 + + + + + + {/* Item 2 */} + + + + + + Diagnose Summary + + + + Gejala + Nyeri dada + + + Tanda + Sesak Napas + + + Main Diagnose + + J46 Status asthmaticus, Acute severe asthma + + + + Diagnosis pembanding + K21 Gastro-oesophageal reflux disease + + + + + + {/* Item 3 */} + + + + + + Services + + + + + Evakuasi medis + Land Transportation + + + Rumah sakit + Primaya Hospital + + + + Tanggal mulai + 17 Aug 2022 + + + Selesai + 18 Aug 2022 + + + + Daftar layanan + + Inpatient, Medivac (Medical Evacuation) + + + + + + + + + + + } label="Daily Monitoring" {...a11yProps(0)} /> + } + label="Item Two" + {...a11yProps(1)} + /> + + + + Item One + + + Item Two + + + + + + ); +} diff --git a/frontend/client-portal/src/pages/ClaimReport/Index.tsx b/frontend/client-portal/src/pages/ClaimReport/Index.tsx new file mode 100755 index 00000000..d4976342 --- /dev/null +++ b/frontend/client-portal/src/pages/ClaimReport/Index.tsx @@ -0,0 +1,46 @@ +// @mui +import { Card, Container, Grid, TableBody, TableCell, TableRow } from '@mui/material'; +// components +import Page from '../../components/Page'; +// utils +import useSettings from '../../hooks/useSettings'; +// theme +import palette from '../../theme/palette'; +// section +import CardClaimStatus from '../../sections/claim-report/CardClaimStatus'; + +// ---------------------------------------------------------------------- + +const listClaimItems = [ + { name: 'Requested', value: 15, color: palette.dark.primary.dark }, + { name: 'Approval', value: 20, color: palette.dark.warning.dark }, + { name: 'Disbrusment', value: 20, color: palette.dark.success.dark }, + { name: 'Rejected', value: 20, color: palette.dark.error.dark }, +]; + +const testingData = [ + { label: 'Member ID', value: 'member_id' }, + { label: 'Name', value: 'name' }, + { label: 'Divisi', value: 'division_id' }, +]; + +// ---------------------------------------------------------------------- + +export default function Drugs() { + const { themeStretch } = useSettings(); + + return ( + + + + + + + + + + + + + ); +} diff --git a/frontend/client-portal/src/pages/ClaimReports/Create.tsx b/frontend/client-portal/src/pages/ClaimReports/Create.tsx deleted file mode 100755 index e0527248..00000000 --- a/frontend/client-portal/src/pages/ClaimReports/Create.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as Yup from 'yup'; -import { yupResolver } from "@hookform/resolvers/yup"; -import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material"; -import { useForm } from "react-hook-form"; -import { useParams } from "react-router-dom"; -import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; -import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from "../../../components/hook-form"; -import Page from "../../../components/Page"; -import useSettings from "../../../hooks/useSettings"; -import { useMemo, useState } from 'react'; -import Form from "./Form"; - - -export default function Divisions() { - const { themeStretch } = useSettings(); - - const [isEdit, setIsEdit] = useState(false); - - const [currentFormularium, setCurrentFormularium] = useState({}); - - const NewDivisionSchema = Yup.object().shape({ - name: Yup.string().required('Name is required'), - code: Yup.string().required('Corporate Code is required'), - active: Yup.boolean().required('Corporate Status is required'), - }); - - const defaultValues = useMemo( - () => ({ - code: '', - }), - [] - ); - - const methods = useForm({ - resolver: yupResolver(NewDivisionSchema), - defaultValues, - }); - - const { - reset, - watch, - control, - setValue, - getValues, - setError, - handleSubmit, - formState: { isSubmitting }, - } = methods; - - const onSubmit = async (data: any) => { - console.log(data) - }; - - const pageTitle = 'Create Formularium'; - return ( - - - - - - - - -
- - - - - ); -} diff --git a/frontend/client-portal/src/pages/ClaimReports/Form.tsx b/frontend/client-portal/src/pages/ClaimReports/Form.tsx deleted file mode 100755 index 9ec18471..00000000 --- a/frontend/client-portal/src/pages/ClaimReports/Form.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import * as Yup from 'yup'; -import { useSnackbar } from 'notistack'; -import { useNavigate } from 'react-router-dom'; -import { useCallback, useEffect, useMemo, useRef, useState } 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, - Button, - ButtonGroup, - Card, - FormHelperText, - Grid, - Stack, - Typography, -} 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 { Corporate } from '../../../@types/corporates'; -import axios from '../../../utils/axios'; -import { fCurrency } from '../../../utils/formatNumber'; - -const LabelStyle = styled(Typography)(({ theme }) => ({ - ...theme.typography.subtitle2, - color: theme.palette.text.secondary, - marginBottom: theme.spacing(1), -})); - -interface FormValuesProps extends Partial { - taxes: boolean; - inStock: boolean; -} - -type Props = { - isEdit: boolean; - currentFormularium?: Corporate; -}; - -export default function FormulariumForm({ isEdit, currentFormularium }: 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'), - // code: Yup.string().required('Corporate Code is required'), - // file: Yup.boolean().required('Corporate Status is required'), - }); - - const defaultValues = useMemo( - () => ({ - code: currentFormularium?.code || '', - name: currentFormularium?.name || '', - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [currentFormularium] - ); - - const methods = useForm({ - resolver: yupResolver(NewCorporateSchema), - defaultValues, - }); - - const { - reset, - watch, - control, - setValue, - getValues, - setError, - handleSubmit, - formState: { isSubmitting }, - } = methods; - - const values = watch(); - - useEffect(() => { - if (isEdit && currentFormularium) { - reset(defaultValues); - } - if (!isEdit) { - reset(defaultValues); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isEdit, currentFormularium]); - - const onSubmit = async (data: FormValuesProps) => { - try { - if (!isEdit) { - const response = await axios.post('/master/formulariums', data); - } else { - const response = await axios.put('/master/formulariums/' + currentFormularium?.id ?? '', data); - } - reset(); - enqueueSnackbar(!isEdit ? 'Formularium Created Successfully!' : 'Formularium Udpated Successfully!', { variant: 'success' }); - navigate('/master/formularium'); - } catch (error: any) { - if (error && error.response.status === 422) { - for (const [key, value] of Object.entries(error.response.data.errors)) { - setError(key, { message: value[0] }); - enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); - } - } - else { - enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); - } - } - - const ascent = document?.querySelector("ascent"); - if (ascent != null) { - ascent.innerHTML = ""; - } - }; - - const handleDrop = useCallback( - (acceptedFiles) => { - setValue( - 'logo', - acceptedFiles.map((file: Blob | MediaSource) => - Object.assign(file, { - preview: URL.createObjectURL(file), - }) - ) - ); - }, - [setValue] - ); - - const handleRemove = (file: File | string) => { - setValue('logo', null); - }; - - const linking_rules_checkbox_name = "linking_rules" - const linking_tools = [ - { - "value" : "nrik", - "label" : "No. KTP" - }, - { - "value" : "nik", - "label" : "Nomor Induk Karyawan (NIK)" - }, - { - "value" : "member_id", - "label" : "Member ID" - }, - { - "value" : "phone", - "label" : "Nomor Telepon" - }, - { - "value" : "email", - "label" : "E-Mail" - }, - ] - - - const importForm = useRef(null) - const [anchorEl, setAnchorEl] = useState(null); - const [currentImportFileName, setCurrentImportFileName] = useState(null); - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleImportButton = () => { - if (importForm?.current) { - handleClose(); - importForm.current ? importForm.current.click() : console.log('No File selected'); - } else { - alert('No file selected') - } - } - - const handleCancelImportButton = () => { - importForm.current.value = ""; - importForm.current.dispatchEvent(new Event("change", { bubbles: true })); - } - - const handleImportChange = (event: any) => { - if (event.target.files[0]) { - setCurrentImportFileName(event.target.files[0].name) - } else { - setCurrentImportFileName(null); - } - } - - - return ( - - - - - Formularium Detail - -
- - {(!(currentFormularium?.id) && Will be generated if empty)} -
- - - - Formularium Drug List Import - - - - - {(currentImportFileName && )} - - - - {!isEdit ? 'Save New Corporate' : 'Save Update'} - - -
-
- ); -}; diff --git a/frontend/client-portal/src/pages/Dashboard.tsx b/frontend/client-portal/src/pages/Dashboard/Dashboard.tsx similarity index 52% rename from frontend/client-portal/src/pages/Dashboard.tsx rename to frontend/client-portal/src/pages/Dashboard/Dashboard.tsx index 6d1188d0..6947c31d 100755 --- a/frontend/client-portal/src/pages/Dashboard.tsx +++ b/frontend/client-portal/src/pages/Dashboard/Dashboard.tsx @@ -1,25 +1,29 @@ // @mui -import { Container, Grid, Typography } from '@mui/material'; +import { Typography, Container, Grid } from '@mui/material'; // hooks -import useSettings from '../hooks/useSettings'; +import useSettings from '../../hooks/useSettings'; // components -import Page from '../components/Page'; -import Popup from '../components/Popup'; -// import axios from '../utils/axios'; -// DashboardComponent -import BalanceCard from '../sections/dashboard/BalanceCard'; -import NotificationCard from '../sections/dashboard/NotificationCard'; -import DashboardTable from '../sections/dashboard/DashboardTable'; -// React -import { useState } from 'react'; +import Page from '../../components/Page'; +// Table +import List from './List'; +// theme +import CardNotification from '../../sections/dashboard/CardNotification'; +import CardBalance from '../../sections/dashboard/CardBalance'; + +// ---------------------------------------------------------------------- + +const itemList = [ + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '08:00 WIB' }, + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '09:00 WIB' }, + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '10:00 WIB' }, + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '11:00 WIB' }, +]; // ---------------------------------------------------------------------- export default function Dashboard() { const { themeStretch } = useSettings(); - const [openPopup, setOpenPopup] = useState(false); - // const { logout } = useAuth(); // const [corporate, setCorporate] = useState({}); // const loadSomething = () => { @@ -46,18 +50,18 @@ export default function Dashboard() { - + - + - + {/* */} - + {/* */} ); } diff --git a/frontend/client-portal/src/pages/Medicines/Create.tsx b/frontend/client-portal/src/pages/Medicines/Create.tsx deleted file mode 100755 index 06952935..00000000 --- a/frontend/client-portal/src/pages/Medicines/Create.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// @mui -import { Button, Container, Typography } from '@mui/material'; -// hooks -import useSettings from '../../hooks/useSettings'; -// components -import Page from '../../components/Page'; -import axios from '../../utils/axios'; -import useAuth from '../../hooks/useAuth'; -import { Link } from 'react-router-dom'; - -// ---------------------------------------------------------------------- - -export default function PageOne() { - const { themeStretch } = useSettings(); - - const { logout } = useAuth(); - - const loadSomething = () => { - console.log('Loading Something') - } - - return ( - - - - Create Obat - - qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq - - - ); -} diff --git a/frontend/client-portal/src/pages/auth/Login.tsx b/frontend/client-portal/src/pages/auth/Login.tsx index a3686873..9206e08d 100755 --- a/frontend/client-portal/src/pages/auth/Login.tsx +++ b/frontend/client-portal/src/pages/auth/Login.tsx @@ -1,9 +1,6 @@ -import { capitalCase } from 'change-case'; // @mui import { styled } from '@mui/material/styles'; -import { Box, Card, Divider, Grid, Link, Stack, Tooltip, Typography } from '@mui/material'; -// hooks -import useAuth from '../../hooks/useAuth'; +import { Box, Card, Divider, Grid, Link, Stack, Typography } from '@mui/material'; // components import Page from '../../components/Page'; import Image from '../../components/Image'; @@ -36,10 +33,8 @@ const ContentStyle = styled(Card)(({ theme }) => ({ // ---------------------------------------------------------------------- export default function Login() { - const { method } = useAuth(); - const location = useLocation(); + const { state } = useLocation(); const [formPhone, setFormPhone] = useState(false); - // const { setForm } = location.state; const handlerChange = (event: any, setForm: boolean) => { event.preventDefault(); @@ -47,9 +42,11 @@ export default function Login() { }; useEffect(() => { - console.log('setForm'); - // setFormPhone(setForm ? setForm : true); - }, []); + if (state !== null) { + setFormPhone(state.formPhone); + } + console.log(state); + }, [state]); return ( @@ -61,10 +58,7 @@ export default function Login() { - - - - + Sign in to LinkSehat @@ -75,29 +69,31 @@ export default function Login() { - {formPhone === false ? : } + {formPhone ? ( + + ) : ( + + )} Atau - {formPhone === false ? ( + {formPhone ? ( handlerChange(event, true)} - > - Masuk menggunakan nomor handphone - - ) : ( - handlerChange(event, false)} > Masuk menggunakan email + ) : ( + handlerChange(event, true)} + > + Masuk menggunakan nomor handphone + )} diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index 9773025a..1d7ae5f4 100755 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -38,25 +38,17 @@ export default function Router() { ), }, { - path: 'otp-validation', + path: 'verify-code', element: ( - + ), }, - // { path: 'login-unprotected', element: }, - // { path: 'register-unprotected', element: }, - // { path: 'reset-password', element: }, - // { path: 'verify', element: }, ], }, - // { - // path: '/', - // element: , - // }, { path: '/', element: ( @@ -72,33 +64,44 @@ export default function Router() { path: 'dashboard', element: , }, + ], + }, + { + path: '/alarm-center', + element: ( + + + + + + ), + children: [ { - path: 'claim-reports', - element: , + element: , + index: true, + }, + { + path: 'service-monitoring/:id', + element: , + }, + ], + }, + { + path: '/claim-report', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, }, ], }, - // { - // path: '/dashboard', - // element: , - // children: [ - // { element: , index: true }, - // { path: 'one', element: - // }, - // { path: 'two', element: - // }, - // { path: 'three', element: - // }, - // { - // path: 'user', - // children: [ - // { element: , index: true }, - // { path: 'four', element: }, - // { path: 'six', element: }, - // ], - // }, - // ], - // }, { path: '*', element: , @@ -111,12 +114,19 @@ export default function Router() { ]); } +// Auth const Login = Loadable(lazy(() => import('../pages/auth/Login'))); -const OtpValidation = Loadable(lazy(() => import('../pages/auth/OtpValidation'))); +const VerifyCode = Loadable(lazy(() => import('../pages/auth/VerifyCode'))); // Dashboard -const Dashboard = Loadable(lazy(() => import('../pages/Dashboard'))); +const Dashboard = Loadable(lazy(() => import('../pages/Dashboard/Dashboard'))); const NotFound = Loadable(lazy(() => import('../pages/Page404'))); -// Claim Reports -const ClaimReports = Loadable(lazy(() => import('../pages/ClaimReports/Index'))); +// Alarm Center +const AlarmCenter = Loadable(lazy(() => import('../pages/AlarmCenter/Index'))); +const AlarmCenterServiceMonitoring = Loadable( + lazy(() => import('../pages/AlarmCenter/ServiceMonitoring')) +); + +// Claim Report +const ClaimReport = Loadable(lazy(() => import('../pages/ClaimReport/Index'))); diff --git a/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx index 89926836..70ae2af1 100755 --- a/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx +++ b/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx @@ -16,9 +16,16 @@ import { FormProvider, RHFTextField } from '../../../components/hook-form'; type FormValuesProps = { email: string; + afterSubmit?: string; }; -export default function LoginForm() { +interface Props { + formPhone: boolean; +} + +// ---------------------------------------------------------------------- + +export default function LoginForm({ formPhone }: Props) { const { login } = useAuth(); const navigate = useNavigate(); @@ -28,8 +35,13 @@ export default function LoginForm() { email: Yup.string().email('Email must be a valid email address').required('Email is required'), }); + const defaultValues = { + email: '', + }; + const methods = useForm({ resolver: yupResolver(LoginSchema), + defaultValues, }); const { @@ -43,10 +55,8 @@ export default function LoginForm() { try { await login(data.email); - navigate('/dashboard'); - } catch (error) { - console.error(error); - + navigate('/auth/verify-code', { state: { phoneOrEmail: data.email, formPhone } }); + } catch (error: any) { reset(); if (isMountedRef.current) { diff --git a/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx index f1a35f1a..1ab770b2 100755 --- a/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx +++ b/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx @@ -1,7 +1,5 @@ import * as Yup from 'yup'; -import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import axios from '../../../utils/axios'; // form import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; @@ -10,6 +8,7 @@ import { Stack, Alert, InputAdornment } from '@mui/material'; import { LoadingButton } from '@mui/lab'; // components import { FormProvider, RHFTextField } from '../../../components/hook-form'; +import useAuth from '../../../hooks/useAuth'; // ---------------------------------------------------------------------- @@ -18,7 +17,12 @@ type FormValuesProps = { afterSubmit?: string; }; -export default function LoginPhoneForm() { +interface Props { + formPhone: boolean; +} + +export default function LoginPhoneForm({ formPhone }: Props) { + const { login } = useAuth(); const navigate = useNavigate(); const LoginSchema = Yup.object().shape({ @@ -43,8 +47,8 @@ export default function LoginPhoneForm() { const onSubmit = async (data: FormValuesProps) => { try { - await axios.post('/otp-request', { phone_or_email: 0 + data.phone }); - navigate('/auth/otp-validation', { state: { phone_or_email: 0 + data.phone } }); + await login(0 + data.phone); + navigate('/auth/verify-code', { state: { phoneOrEmail: 0 + data.phone, formPhone } }); } catch (error: any) { reset(); setError('afterSubmit', { ...error, message: error.response.data.message }); diff --git a/frontend/client-portal/src/sections/auth/login/index.ts b/frontend/client-portal/src/sections/auth/login/index.ts index be6c6913..9722682f 100755 --- a/frontend/client-portal/src/sections/auth/login/index.ts +++ b/frontend/client-portal/src/sections/auth/login/index.ts @@ -1,2 +1,2 @@ export { default as LoginEmailForm } from './LoginEmailForm'; -export { default as LoginPhoneForm } from './LoginPhoneForm'; +export { default as LoginPhoneForm } from './LoginPhoneForm'; \ No newline at end of file diff --git a/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx b/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx index ffadb2a8..4e136299 100755 --- a/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx +++ b/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx @@ -7,6 +7,7 @@ import { useForm, Controller } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; // @mui import { OutlinedInput, Stack } from '@mui/material'; +import useAuth from '../../../hooks/useAuth'; // routes // import { PATH_DASHBOARD } from '../../../routes/paths'; @@ -19,13 +20,17 @@ type FormValuesProps = { code4: string; }; +type Props = { + phoneOrEmail: string; +}; + type ValueNames = 'code1' | 'code2' | 'code3' | 'code4'; -export default function VerifyCodeForm() { +export default function VerifyCodeForm({ phoneOrEmail }: Props) { const navigate = useNavigate(); const location = useLocation(); + const { validateOtp } = useAuth(); const { enqueueSnackbar } = useSnackbar(); - const { phone_or_email } = location.state; const VerifyCodeSchema = Yup.object().shape({ @@ -64,12 +69,10 @@ export default function VerifyCodeForm() { const onSubmit = async (data: FormValuesProps) => { try { - await new Promise((resolve) => setTimeout(resolve, 500)); - console.log('code:', Object.values(data).join('')); - - enqueueSnackbar('Verify success!'); - - // navigate('/dashboard', { replace: true }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + await validateOtp(phoneOrEmail, Object.values(data).join('')); + navigate('/dashboard'); + enqueueSnackbar('Verify success!', { variant: 'success' }); } catch (error) { console.error(error); } @@ -111,7 +114,7 @@ export default function VerifyCodeForm() { return ( - + {Object.keys(values).map((name, index) => ( ) => handleChangeWithNextField(event, field.onChange) } diff --git a/frontend/client-portal/src/sections/claim-report/CardClaimStatus.tsx b/frontend/client-portal/src/sections/claim-report/CardClaimStatus.tsx new file mode 100644 index 00000000..595c1dec --- /dev/null +++ b/frontend/client-portal/src/sections/claim-report/CardClaimStatus.tsx @@ -0,0 +1,102 @@ +// @mui +import { Grid, Card, Typography, Stack } from '@mui/material'; +import { styled } from '@mui/material/styles'; +// theme +import palette from '../../theme/palette'; + +// ---------------------------------------------------------------------- + +interface ClaimStatusType { + name: string; + value: number; + color: string; +} + +interface PropsCardClaimStatus { + data?: ClaimStatusType[]; +} + +// ---------------------------------------------------------------------- + +const RootStyle = styled(Card)(({ theme }) => ({ + boxShadow: 'none', + padding: theme.spacing(2), + color: 'black', + backgroundColor: theme.palette.grey[200], +})); + +// ---------------------------------------------------------------------- + +const defaultData = [ + { name: 'Requested', value: 0, color: palette.dark.primary.dark }, + { name: 'Approval', value: 0, color: palette.dark.warning.dark }, + { name: 'Disbrusment', value: 0, color: palette.dark.success.dark }, + { name: 'Rejected', value: 0, color: palette.dark.error.dark }, +]; + +// ---------------------------------------------------------------------- + +export default function CardClaimStatus({ data }: PropsCardClaimStatus) { + return ( + + + Claim Status + + + {data + ? data.map(({ name, value, color }: ClaimStatusType, key) => ( + + + + {name} + + + {value} + + + Cases + + + + )) + : defaultData.map(({ name, value, color }: ClaimStatusType, key) => ( + + + + {name} + + + {value} + + + Cases + + + + ))} + + + ); +} diff --git a/frontend/client-portal/src/sections/dashboard/CardBalance.tsx b/frontend/client-portal/src/sections/dashboard/CardBalance.tsx new file mode 100644 index 00000000..fb2ae52b --- /dev/null +++ b/frontend/client-portal/src/sections/dashboard/CardBalance.tsx @@ -0,0 +1,161 @@ +// @mui +import { styled } from '@mui/material/styles'; +import { + Button, + Card, + Typography, + LinearProgress, + linearProgressClasses, + Stack, +} from '@mui/material'; +// components +import Iconify from '../../components/Iconify'; +// React +import { useState } from 'react'; +// utils +import { fCurrency } from '../../utils/formatNumber'; +// +import DialogTopUpLimit from './DialogTopUpLimit'; + +// ---------------------------------------------------------------------- + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type NotificationProps = { + data?: DataContent[]; +}; + +// ---------------------------------------------------------------------- + +const RootBalanceStyle = styled(Card)(({ theme }) => ({ + boxShadow: 'none', + padding: theme.spacing(3), + color: 'black', + backgroundColor: theme.palette.grey[200], + maxHeight: '240px', +})); + +const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ + height: 10, + borderRadius: 6, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[theme.palette.mode === 'light' ? 300 : 800], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 6, + backgroundColor: theme.palette.primary.main, + }, +})); + +// ---------------------------------------------------------------------- + +const INITIAL = '500.000.000'; +const TOTAL = 375000000; +const PERCENT = 75; + +// ---------------------------------------------------------------------- + +export default function CardBalance({ data }: NotificationProps) { + const [openDialog, setOpenDialog] = useState(false); + const [dialogTitle, setDialogTitle] = useState(''); + const [isDialog, setIsDialog] = useState(''); + + const clickHandler = (isDialog: string) => { + switch (isDialog) { + case 'submitClaim': + setDialogTitle('Notification'); + setIsDialog(isDialog); + setOpenDialog(true); + break; + case 'topUpLimit': + setDialogTitle('Top Up Limit'); + setIsDialog(isDialog); + setOpenDialog(true); + break; + + default: + break; + } + }; + + return ( + + <> + +
+ + Total Limit + + {fCurrency(TOTAL)} + / {INITIAL} +
+ + + + {PERCENT}% + + +
+ + + + + + + + Lock Fund ( 25% ) + + + + 125.000.000 / 125.000.000 + + + + + + + + + + {/* {isDialog === 'submitClaim' && ( + + )} */} + + {isDialog === 'topUpLimit' && ( + + )} +
+ ); +} diff --git a/frontend/client-portal/src/sections/dashboard/CardNotification.tsx b/frontend/client-portal/src/sections/dashboard/CardNotification.tsx new file mode 100644 index 00000000..a0d07784 --- /dev/null +++ b/frontend/client-portal/src/sections/dashboard/CardNotification.tsx @@ -0,0 +1,143 @@ +// @mui +import { styled } from '@mui/material/styles'; +import { Button, Card, Typography, Link, Divider, Stack } from '@mui/material'; +import { ChevronRight } from '@mui/icons-material'; +// components +import Iconify from '../../components/Iconify'; +// Section +import DialogNotification from './DialogNotification'; +import DialogDetailClaim from './DialogDetailClaim'; +// React +import { useState } from 'react'; + +// ---------------------------------------------------------------------- + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type NotificationProps = { + data?: DataContent[]; +}; + +// ---------------------------------------------------------------------- + +const RootNotificationStyle = styled(Card)(({ theme }) => ({ + boxShadow: 'none', + padding: '1rem 0.5rem', + color: 'black', + backgroundColor: theme.palette.grey[200], + maxHeight: '240px', +})); + +const ItemNotificationStyle = styled(Card)(({ theme }) => ({ + boxShadow: 'none', + padding: theme.spacing(1), + borderRadius: 0.5, + color: 'black', +})); + +// ---------------------------------------------------------------------- + +export default function CardNotification({ data }: NotificationProps) { + const [openDialog, setOpenDialog] = useState(false); + const [dialogTitle, setDialogTitle] = useState(''); + const [isDialog, setIsDialog] = useState(''); + + const clickHandler = (isDialog: string) => { + switch (isDialog) { + case 'allNotification': + setDialogTitle('Notification'); + setIsDialog(isDialog); + setOpenDialog(true); + break; + case 'infoDetail': + setDialogTitle('Claim Details'); + setIsDialog(isDialog); + setOpenDialog(true); + break; + + default: + break; + } + }; + + return ( + + + + + + Notification + + + + + + + + {data + ? data.map(({ info, date, time }, key) => ( +
+ {key >= 1 ? : ''} + + + {info} + clickHandler('infoDetail')} + > + Info Detail + + + + {date} + {time} + + +
+ )) + : ''} +
+ + {isDialog === 'allNotification' && ( + + )} + + {isDialog === 'infoDetail' && ( + + )} +
+ ); +} diff --git a/frontend/client-portal/src/sections/dashboard/DialogDetailClaim.tsx b/frontend/client-portal/src/sections/dashboard/DialogDetailClaim.tsx new file mode 100644 index 00000000..3aba83d1 --- /dev/null +++ b/frontend/client-portal/src/sections/dashboard/DialogDetailClaim.tsx @@ -0,0 +1,175 @@ +// @mui +import { + Button, + Box, + Stepper, + Step, + StepLabel, + Card, + Typography, + Divider, + Stack, +} from '@mui/material'; +import { Add } from '@mui/icons-material'; +// components +import MuiDialog from '../../components/MuiDialog'; +// theme +import palette from '../../theme/palette'; +// React +import { ReactElement } from 'react'; + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + data?: DataContent[]; +}; + +const steps = ['Review', 'Approval', 'Disbursement']; + +const DialogDetailClaim = ({ title, openDialog, setOpenDialog, data }: MuiDialogProps) => { + const getContent = () => ( + <> + + + Claim Request + + + Submission date + 15 / 05 / 2022 + + + + + {steps.map((label) => ( + + {label} + + ))} + + + + + 17 Mei 2022 + + + + + + {/* Item 1 */} + + + 09:10 WIB + + Approval + + + + + + Details : mohon melengkapi kekurangan dokumen + + + Lab pemeriksaan darah + + + + + {/* Item 2 */} + + + 09:00 WIB + + Approval + + + + + + Details : Penilaian Dokter + + + + {/* Item 3 */} + + + 08:00 WIB + + Review + + + + + + Details : Klaim Diajukan + + + + + + + ); + + return ( + + ); +}; + +export default DialogDetailClaim; diff --git a/frontend/client-portal/src/sections/dashboard/DialogNotification.tsx b/frontend/client-portal/src/sections/dashboard/DialogNotification.tsx new file mode 100644 index 00000000..c93a27b2 --- /dev/null +++ b/frontend/client-portal/src/sections/dashboard/DialogNotification.tsx @@ -0,0 +1,93 @@ +// react +import { ReactElement, useState } from 'react'; +// mui +import { Card, Divider, Link, Stack, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; +// Component +import MuiDialog from '../../components/MuiDialog'; +// Sections +import DialogDetailClaim from './DialogDetailClaim'; + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + data?: DataContent[]; +}; + +const ItemNotificationStyle = styled(Card)(({ theme }) => ({ + boxShadow: 'none', + padding: theme.spacing(1), + borderRadius: 0.5, + color: 'black', +})); + +const DialogNotification = ({ title, openDialog, setOpenDialog, data }: MuiDialogProps) => { + const [openDialogClaim, setOpenDialogClaim] = useState(false); + const [dialogTitleClaim, setDialogTitleClaim] = useState(''); + + const clickHandler = () => { + setDialogTitleClaim('Claim Details'); + setOpenDialogClaim(true); + }; + + const getContent = () => ( + + + {data + ? data.map(({ info, date, time }: DataContent, key) => ( +
+ {key >= 1 ? : ''} + + + {info} + + Info Detail + + + + {date} + {time} + + +
+ )) + : ''} +
+
+ ); + + return ( + <> + + + + + ); +}; + +export default DialogNotification; diff --git a/frontend/client-portal/src/sections/dashboard/DialogTopUpLimit.tsx b/frontend/client-portal/src/sections/dashboard/DialogTopUpLimit.tsx new file mode 100644 index 00000000..c433a70d --- /dev/null +++ b/frontend/client-portal/src/sections/dashboard/DialogTopUpLimit.tsx @@ -0,0 +1,209 @@ +// @mui +import { styled } from '@mui/material/styles'; +import { + Typography, + LinearProgress, + linearProgressClasses, + Stack, + FormControlLabel, +} from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import Checkbox from '@mui/material/Checkbox'; +// components +import MuiDialog from '../../components/MuiDialog'; +import { FormProvider, RHFTextField } from '../../components/hook-form'; +// React +import { ReactElement, useEffect, useState } from 'react'; +import { fCurrency } from '../../utils/formatNumber'; +// yup +import * as Yup from 'yup'; +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; + +// ---------------------------------------------------------------------- + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + data?: DataContent[]; +}; + +type FormValuesProps = { + topup: string; +}; + +// ---------------------------------------------------------------------- + +const testData = { + companyName: 'PT. Aman Mineral', + policyNumber: 12345678, + totalMembers: 50, + totalCases: 100, + totalPersen: 75, + myLimit: '375.000.000', + totalLimit: 500000000, +}; + +const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ + height: 10, + borderRadius: 6, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[theme.palette.mode === 'light' ? 300 : 800], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 6, + background: 'linear-gradient(270deg, #19BBBB 38.42%, #FF9565 76.21%, #FE7253 104.02%)', + }, +})); + +// ---------------------------------------------------------------------- + +const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogProps) => { + const [isDisabledCheckbox, setIsDisabledCheckbox] = useState(false); + const [isDisabledButton, setIsDisabledButton] = useState(true); + + const TopUpSchema = Yup.object().shape({ + topup: Yup.string() + /* + // @ts-ignore */ + .test('limit', 'Maximum Top Up Rp. 5.000.000', (val) => (val > 5000000 ? false : true)), + }); + + const defaultValues = { + topup: '0', + }; + + const methods = useForm({ + resolver: yupResolver(TopUpSchema), + defaultValues, + }); + + const { + setValue, + reset, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + useEffect(() => { + if (openDialog === false) { + reset(); + } + }, [openDialog, reset]); + + const onSubmit = async (data: FormValuesProps) => { + reset(); + }; + + const onCheckHandler = (data: FormValuesProps) => { + setIsDisabledCheckbox(!isDisabledCheckbox); + setValue('topup', '5000000'); + }; + + const onTopupHandler = (value: string) => { + value === '0' || value === '' ? setIsDisabledButton(true) : setIsDisabledButton(false); + setValue('topup', value); + }; + + const getContent = () => ( + + + + Company Name + + {testData.companyName} + + + + Policy Number + + {testData.policyNumber} + + + + + Total Member + + {testData.totalMembers} Person + + + + Total Cases + + {testData.totalCases} Cases + + + + + + Company Pooled Fund + {fCurrency(testData.myLimit)} + + / {testData.totalLimit} + + + + {testData.totalPersen}% + + + + + + + Top Up Limit + + + onTopupHandler(e.target.value)} + /> + } + label={'Max ' + fCurrency(5000000)} + onChange={handleSubmit(onCheckHandler)} + /> + + + Login + + + + + ); + + return ( + + ); +}; + +export default DialogTopUpLimit; diff --git a/frontend/client-portal/src/sections/dashboard/SomethingUsage.tsx b/frontend/client-portal/src/sections/dashboard/SomethingUsage.tsx deleted file mode 100755 index 4fbaafc3..00000000 --- a/frontend/client-portal/src/sections/dashboard/SomethingUsage.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import merge from 'lodash/merge'; -import ReactApexChart from 'react-apexcharts'; -// @mui -import { styled } from '@mui/material/styles'; -import { Card, Typography, Stack } from '@mui/material'; -// utils -import { fCurrency, fPercent } from '../../utils/formatNumber'; -// components -import Iconify from '../../components/Iconify'; -import BaseOptionChart from '../../components/chart/BaseOptionChart'; - -// ---------------------------------------------------------------------- - -const RootStyle = styled(Card)(({ theme }) => ({ - boxShadow: 'none', - padding: theme.spacing(3), - color: theme.palette.primary.darker, - backgroundColor: theme.palette.primary.lighter, -})); - -// ---------------------------------------------------------------------- - -const INITIAL = 500000000 -const TOTAL = 257907000; -const PERCENT = -3; -const CHART_DATA = [{ data: [100, 99, 99, 85, 74, 57, 54, 51] }]; - -export default function SomethingUsage() { - const chartOptions = merge(BaseOptionChart(), { - chart: { sparkline: { enabled: true } }, - xaxis: { labels: { show: true } }, - yaxis: { labels: { show: false } }, - stroke: { width: 4 }, - legend: { show: false }, - grid: { show: false }, - tooltip: { - marker: { show: false }, - y: { - formatter: (seriesName: string) => (seriesName) + "%", - title: { - formatter: () => '', - }, - }, - }, - fill: { gradient: { opacityFrom: 0, opacityTo: 0 } }, - }); - - return ( - - -
- - {fCurrency(INITIAL)} - - Remaining Balance - {fCurrency(TOTAL)} -
- -
- - = 0 ? 'eva:trending-up-fill' : 'eva:trending-down-fill'} - /> - - {PERCENT > 0 && '+'} - {fPercent(PERCENT)} - - - -  than last month - -
-
- - -
- ); -} diff --git a/frontend/client-portal/src/theme/overrides/Backdrop.ts b/frontend/client-portal/src/theme/overrides/Backdrop.ts index 18df98ec..745409e8 100755 --- a/frontend/client-portal/src/theme/overrides/Backdrop.ts +++ b/frontend/client-portal/src/theme/overrides/Backdrop.ts @@ -1,20 +1,15 @@ -import { alpha, Theme } from '@mui/material/styles'; +import { Theme } from '@mui/material/styles'; // ---------------------------------------------------------------------- export default function Backdrop(theme: Theme) { - const varLow = alpha(theme.palette.grey[900], 0.48); - const varHigh = alpha(theme.palette.grey[900], 1); return { MuiBackdrop: { styleOverrides: { root: { background: [ - `rgb(22,28,36)`, - `-moz-linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`, - `-webkit-linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`, - `linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)` + `rgb(33,43,54, 0.7)`, ], '&.MuiBackdrop-invisible': { background: 'transparent'