client portal dashboard & claim-reports

This commit is contained in:
Muhammad Fajar
2022-11-10 10:00:24 +07:00
parent ce8f59c2ff
commit ca36d1a593
14 changed files with 513 additions and 429 deletions

View File

@@ -1,28 +1,21 @@
import * as Yup from 'yup';
import { useState } from 'react';
import { Link as RouterLink, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
// form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
// @mui
import { Link, Stack, Alert, IconButton, InputAdornment } from '@mui/material';
import { Stack, Alert } from '@mui/material';
import { LoadingButton } from '@mui/lab';
// routes
import { PATH_AUTH } from '../../../routes/paths';
// hooks
import useAuth from '../../../hooks/useAuth';
import useIsMountedRef from '../../../hooks/useIsMountedRef';
// components
import Iconify from '../../../components/Iconify';
import { FormProvider, RHFTextField, RHFCheckbox } from '../../../components/hook-form';
import { FormProvider, RHFTextField } from '../../../components/hook-form';
// ----------------------------------------------------------------------
type FormValuesProps = {
email: string;
password: string;
remember: boolean;
afterSubmit?: string;
};
export default function LoginForm() {
@@ -31,24 +24,15 @@ export default function LoginForm() {
const isMountedRef = useIsMountedRef();
const [showPassword, setShowPassword] = useState(false);
const LoginSchema = Yup.object().shape({
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
password: Yup.string().required('Password is required'),
});
const defaultValues = {
email: 'admin@linksehat.dev',
password: 'password',
remember: true,
};
const methods = useForm<FormValuesProps>({
resolver: yupResolver(LoginSchema),
defaultValues,
});
const {
reset,
setError,
@@ -58,8 +42,8 @@ export default function LoginForm() {
const onSubmit = async (data: FormValuesProps) => {
try {
const loginResult = await login(data.email, data.password );
await login(data.email);
navigate('/dashboard');
} catch (error) {
console.error(error);
@@ -75,32 +59,10 @@ export default function LoginForm() {
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
<Alert severity='primary'>Email : admin@linksehat.dev & Password : password</Alert>
<Alert severity="info">Masukkan akun yang telah terdaftar</Alert>
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<RHFTextField name="email" label="Email address" />
<RHFTextField
name="password"
label="Password"
type={showPassword ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => setShowPassword(!showPassword)} edge="end">
<Iconify icon={showPassword ? 'eva:eye-fill' : 'eva:eye-off-fill'} />
</IconButton>
</InputAdornment>
),
}}
/>
</Stack>
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ my: 2 }}>
<RHFCheckbox name="remember" label="Remember me" />
<Link component={RouterLink} variant="subtitle2" to={PATH_AUTH.resetPassword}>
Forgot password?
</Link>
</Stack>
<LoadingButton

View File

@@ -0,0 +1,67 @@
// @mui
import { styled } from '@mui/material/styles';
import { Card, Typography, Stack } from '@mui/material';
// theme
import palette from '../../theme/palette';
// ----------------------------------------------------------------------
const RootStyle = styled(Card)(({ theme }) => ({
boxShadow: 'none',
padding: theme.spacing(2),
color: 'black',
backgroundColor: theme.palette.grey[200],
maxHeight: '240px',
}));
// ----------------------------------------------------------------------
interface ClaimStatusType {
name: string;
value: number;
color: string;
}
export default function ClaimStatusCard({ name, value, color }: ClaimStatusType) {
const listItems = [
{ 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 },
];
return (
<RootStyle>
<Stack sx={{ mb: 1 }}>
<Typography variant="body2">Claim Status</Typography>
</Stack>
<Stack direction="row" spacing={2}>
{listItems.map(({ name, value, color }, key) => (
<Card
key={key}
sx={{
paddingX: 1,
borderRadius: 0.75,
borderColor: color,
borderStyle: 'solid',
borderWidth: '1px',
padding: 2,
flex: 1,
textAlign: 'center',
}}
>
<Typography component="p" variant="body2">
{name}
</Typography>
<Typography component="p" variant="h5" sx={{ marginTop: 2 }}>
{value}
</Typography>
<Typography component="p" variant="body2" sx={{ marginTop: 2 }}>
Cases
</Typography>
</Card>
))}
</Stack>
</RootStyle>
);
}

View File

@@ -0,0 +1,313 @@
// @mui
import {
Autocomplete,
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,
TablePagination,
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 { Member } from '../../@types/member';
import Iconify from '../../components/Iconify';
export default function ListTable() {
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<HTMLInputElement>(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 (
<form onSubmit={handleSearchSubmit} style={{ flex: '1' }}>
<TextField
id="search-input"
ref={searchInput}
label="Search"
variant="outlined"
fullWidth
onChange={handleSearchChange}
value={searchText}
/>
</form>
);
}
function ImportForm(props: any) {
// IMPORT
// Create Button Menu
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const createMenu = Boolean(anchorEl);
const importForm = useRef<HTMLInputElement>(null);
const [currentImportFileName, setCurrentImportFileName] = useState(null);
const handleImportChange = (event: any) => {
if (event.target.files[0]) {
setCurrentImportFileName(event.target.files[0].name);
} else {
setCurrentImportFileName(null);
}
};
const options = ['All', 'Option 2'];
const [value, setValue] = React.useState<string | null>(options[0]);
const [inputValue, setInputValue] = React.useState('');
return (
<div>
<Stack direction={'row'} justifyContent="space-between" spacing={2} sx={{ p: 2 }}>
{/* Filter Division */}
<Autocomplete
value={value}
onChange={(event: any, newValue: string | null) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
sx={{ minWidth: 240 }}
renderInput={(params) => <TextField {...params} label="Division" />}
/>
{/* Search */}
<SearchInput onSearch={applyFilter} />
{/* Button Import */}
<Button
id="import-button"
variant="outlined"
startIcon={<Iconify icon="eva:download-fill" />}
sx={{ p: 1.8, minWidth: '104px' }}
// onClick={() => {}}
>
Import
</Button>
{/* <input
type="file"
id="file"
ref={importForm}
style={{ display: 'none' }}
onChange={handleImportChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/> */}
{/* Button Add Task */}
<Button variant="contained" startIcon={<AddIcon />} sx={{ p: 1.8, minWidth: '142px' }}>
Submit Claim
</Button>
</Stack>
</div>
);
}
// 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<typeof createData> }) {
// const { row } = props;
// const [open, setOpen] = React.useState(true);
// return (
// <React.Fragment>
// <TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
// <TableCell>
// <IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
// {open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
// </IconButton>
// </TableCell>
// <TableCell align="left">{row.member_id}</TableCell>
// <TableCell align="left">{row.payor_id}</TableCell>
// <TableCell align="left">{row.name}</TableCell>
// <TableCell align="left">{row.nik}</TableCell>
// <TableCell align="left">{row.nric}</TableCell>
// <TableCell align="right">
// <Button variant="outlined" color="success" size="small">
// Active
// </Button>
// </TableCell>
// {/* <TableCell align="right"><Button variant="outlined" color="error" size="small">Disable</Button></TableCell> */}
// </TableRow>
// {/* COLLAPSIBLE ROW */}
// <TableRow>
// <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
// <Collapse in={open} timeout="auto" unmountOnExit>
// <Box sx={{ borderBottom: 1 }}>
// <Typography variant="body2" gutterBottom component="div">
// <Grid></Grid>
// </Typography>
// </Box>
// </Collapse>
// </TableCell>
// </TableRow>
// </React.Fragment>
// );
// }
// Dummy Default Data
const [dataTableIsLoading, setDataTableLoading] = useState(true);
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>({
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('/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 });
};
useEffect(() => {
loadDataTableData();
}, []);
return (
<Card>
<ImportForm />
{/* The Main Table */}
<TableContainer component={Stack} sx={{ padding: 2, borderRadius: 1 }}>
<Table aria-label="collapsible table">
<TableBody>
<TableRow sx={{ backgroundColor: '#F4F6F8' }}>
<TableCell style={headStyle} align="left">
MemberID
</TableCell>
<TableCell style={headStyle} align="left">
Name
</TableCell>
<TableCell style={headStyle} align="left">
Divisi
</TableCell>
<TableCell style={headStyle} align="left">
Limit
</TableCell>
<TableCell style={headStyle} align="right" width={100}>
Status
</TableCell>
<TableCell style={headStyle} align="right" width={100}>
Action
</TableCell>
</TableRow>
</TableBody>
{dataTableIsLoading ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
No Data Found
</TableCell>
</TableRow>
</TableBody>
) : dataTableData.data.length === 0 ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
No Data
</TableCell>
</TableRow>
</TableBody>
) : (
<TableBody>
{/* {dataTableData.data.map((row) => (
<Row key={row.id} row={row} />
))} */}
Testing
</TableBody>
)}
</Table>
</TableContainer>
{/* <TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/> */}
</Card>
);
}

View File

@@ -25,11 +25,13 @@ const RootStyle = styled(Card)(({ theme }) => ({
// ----------------------------------------------------------------------
const INITIAL = 500000000;
const TOTAL = 250000000;
const PERCENT = 50;
const INITIAL = '500.000.000';
const TOTAL = 375000000;
const PERCENT = 75;
export default function BalanceCard(props: any) {
const { setOpenPopup } = props;
export default function BalanceCard() {
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 6,
@@ -50,7 +52,7 @@ export default function BalanceCard() {
Total Limit
</Typography>
<Typography sx={{ typography: 'body2' }}>{fCurrency(TOTAL)}</Typography>
<Typography sx={{ typography: 'caption' }}>/ {INITIAL}</Typography>
<Typography sx={{ typography: 'caption', color: '#919EAB' }}>/ {INITIAL}</Typography>
</div>
<Stack direction="row" alignItems="center" justifyContent="center">
@@ -60,11 +62,23 @@ export default function BalanceCard() {
</Stack>
</Stack>
<BorderLinearProgress variant="determinate" value={50} sx={{ mb: 1 }} />
<BorderLinearProgress variant="determinate" value={PERCENT} sx={{ mb: 1 }} />
<Stack sx={{ backgroundColor: '#B2E8E8', paddingY: 1, paddingX: 1.5, mb: 2 }}>
<Typography sx={{ typography: 'caption' }}>Lock Fund ( 25% )</Typography>
<Typography sx={{ typography: 'caption' }}>125.000.000 / 125.000.000</Typography>
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:lock-alt"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Lock Fund ( 25% )
</Typography>
</Typography>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
125.000.000 / 125.000.000
</Typography>
</Stack>
<Stack direction="row" spacing={2}>
@@ -72,6 +86,7 @@ export default function BalanceCard() {
variant="outlined"
startIcon={<Iconify icon="bi:clipboard-check-fill" />}
fullWidth={true}
onClick={() => setOpenPopup(true)}
>
Submit Claim
</Button>

View File

@@ -148,7 +148,7 @@ export default function DashboardTable() {
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/> */}
{/* Button Add Task */}
<Button startIcon={<AddIcon />} sx={{ p: 1.8, minWidth: '142px' }}>
<Button variant="contained" startIcon={<AddIcon />} sx={{ p: 1.8, minWidth: '142px' }}>
Add Data
</Button>
</Stack>