Merge branch 'feature/client-portal-dashboard' into staging

This commit is contained in:
Fajar
2023-03-24 15:25:05 +07:00
6 changed files with 225 additions and 138 deletions

View File

@@ -21,12 +21,14 @@ class CorporateMemberService
$corporateEmployee->where('corporate_id', $corporateId);
})
->when($request->input('search'), function (Builder $query, $search) {
$query->where('member_id', 'like', "%" . $search . "%")
->orWhere('name', 'like', "%" . $search . "%");
$query->where(function (Builder $query) use ($search) {
$query->orWhere('members.member_id', 'like', "%" . $search . "%")
->orWhere('members.name', 'like', "%" . $search . "%");
});
})
->when($request->input('division'), function (Builder $division, $division_id) {
$division->whereHas('division', function ($corporateEmployee) use ($division_id) {
$corporateEmployee->where('division_id', $division_id);
->when($request->input('division'), function (Builder $division, $value) {
$division->whereHas('division', function (Builder $corporateEmployee) use ($value) {
$corporateEmployee->where('division_id', $value);
});
})
->when($request->has('orderBy'), function (Builder $query) use ($request) {

View File

@@ -1,4 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import { SelectChangeEvent } from '@mui/material';
import { Dispatch, FormEvent, SetStateAction } from 'react';
/* ------------------------------- pagination ------------------------------- */
export type PaginationTableProps = {
@@ -34,6 +35,13 @@ export type HeadCell<DataType> = {
};
/* -------------------------------------------------------------------------- */
/* ----------------------------- division filter ---------------------------- */
export type DivisionData = {
id: number;
name: string;
};
/* -------------------------------------------------------------------------- */
/* ----------------------------------- row ---------------------------------- */
export type TableListProps<DataType> = {
headCells?: HeadCell<DataType>[];
@@ -62,5 +70,19 @@ export type TableListProps<DataType> = {
appliedParams: {};
setAppliedParams: Dispatch<SetStateAction<{}>>;
};
searchs: {
searchText: string;
setSearchText: Dispatch<SetStateAction<string>>;
handleSearchSubmit: (event: FormEvent<HTMLFormElement>) => void;
};
filters?: {
useFilter: boolean;
config: {
label: string;
divisionValue: string;
divisionData: DivisionData[];
handleDivisionChange: (event: SelectChangeEvent) => void;
};
};
};
/* -------------------------------------------------------------------------- */

View File

@@ -28,7 +28,7 @@ import { visuallyHidden } from '@mui/utils';
/* ---------------------------------- axios --------------------------------- */
import axios from '../utils/axios';
/* ---------------------------------- react --------------------------------- */
import { useContext, useEffect, useState } from 'react';
import { Fragment, useContext, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
/* -------------------------------- component ------------------------------- */
import BaseTablePagination from './BaseTablePagination';
@@ -61,6 +61,8 @@ export default function Table<T>({
orders,
loadings,
params,
filters,
searchs,
}: TableListProps<T>) {
/* ------------------------------- handle sort ------------------------------ */
const handleRequestSort = async (event: React.MouseEvent<unknown>, property: string) => {
@@ -120,46 +122,6 @@ export default function Table<T>({
};
/* -------------------------------------------------------------------------- */
/* ----------------------------- division field ----------------------------- */
// const [divisionValue, setDivisionValue] = useState('all');
// const [divisionData, setDivisionData] = useState([]);
// const handleDivisionChange = (event: SelectChangeEvent) => {
// setDivisionValue(event.target.value as string);
// if (event.target.value === 'all') {
// searchParams.delete('division');
// const params = Object.fromEntries([...searchParams.entries()]);
// setAppliedParams(params);
// } else {
// const params = Object.fromEntries([
// ...searchParams.entries(),
// ['division', event.target.value as string],
// ]);
// setAppliedParams(params);
// }
// };
/* -------------------------------------------------------------------------- */
/* ------------------------------ Search field ------------------------------ */
// const [searchText, setSearchText] = useState('');
// const handleSearchSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// event.preventDefault();
// setIsLoading(true);
// if (searchText === '') {
// searchParams.delete('search');
// const params = Object.fromEntries([...searchParams.entries()]);
// setAppliedParams(params);
// } else {
// const params = Object.fromEntries([...searchParams.entries(), ['search', searchText]]);
// setAppliedParams(params);
// }
// await new Promise((resolve) => setTimeout(resolve, 500));
// setIsLoading(false);
// };
/* -------------------------------------------------------------------------- */
/* ------------------------ button change pagination ------------------------ */
const onPageChangeHandle = async (
event: React.MouseEvent<HTMLButtonElement> | null,
@@ -194,41 +156,58 @@ export default function Table<T>({
<Card>
<Grid container>
{/* Field 1 */}
{/* <Grid item xs={12} paddingX="24px" paddingY="20px">
<Grid item xs={12} paddingX="24px" paddingY="20px">
<Grid container spacing={2}>
<Grid item xs={12} lg={3} xl={2}>
<FormControl fullWidth>
<InputLabel id="simple-division-select-lable">Division</InputLabel>
<Select
labelId="simple-division-select-lable"
id="division-select-lable"
value={divisionValue}
label="Division"
onChange={handleDivisionChange}
>
<MenuItem value="all">All</MenuItem>
{divisionData.map((row: DivisionDataProps, index) => (
<MenuItem key={index} value={row.id}>
{row.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12} lg={9} xl={10}>
<form onSubmit={handleSearchSubmit}>
<TextField
id="search-input"
label="Search"
variant="outlined"
onChange={(event) => setSearchText(event.target.value)}
value={searchText}
fullWidth
/>
</form>
</Grid>
{filters && filters.useFilter ? (
<Fragment>
<Grid item xs={12} lg={3} xl={2}>
<FormControl fullWidth>
<InputLabel id="simple-division-select-lable">Division</InputLabel>
<Select
labelId="simple-division-select-lable"
id="division-select-lable"
value={filters.config.divisionValue}
label="Division"
onChange={filters.config.handleDivisionChange}
>
<MenuItem value="all">All</MenuItem>
{filters.config.divisionData.map((row: DivisionDataProps, index) => (
<MenuItem key={index} value={row.id}>
{row.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12} lg={9} xl={10}>
<form onSubmit={searchs.handleSearchSubmit}>
<TextField
id="search-input"
label="Search"
variant="outlined"
onChange={(event) => searchs.setSearchText(event.target.value)}
value={searchs.searchText}
fullWidth
/>
</form>
</Grid>
</Fragment>
) : (
<Grid item xs={12}>
<form onSubmit={searchs.handleSearchSubmit}>
<TextField
id="search-input"
label="Search"
variant="outlined"
onChange={(event) => searchs.setSearchText(event.target.value)}
value={searchs.searchText}
fullWidth
/>
</form>
</Grid>
)}
</Grid>
</Grid> */}
</Grid>
{/* End Field 1 */}
{/* Field 2 */}
<Grid item xs={12}>

View File

@@ -8,6 +8,7 @@ import {
IconButton,
LinearProgress,
linearProgressClasses,
SelectChangeEvent,
} from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
@@ -22,11 +23,10 @@ import { Stack } from '@mui/system';
import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate';
import { PolicyProps } from '../../@types/policy';
import Table from '../../components/Table';
import { HeadCell, Order, PaginationTableProps } from '../../@types/table';
import { DivisionData, HeadCell, Order, PaginationTableProps } from '../../@types/table';
import { useSearchParams } from 'react-router-dom';
import palette from '../../theme/palette';
import { MoreVert as MoreVertIcon } from '@mui/icons-material';
import TableList from '../../sections/dashboard/TableList';
import { fSplit } from '../../utils/formatNumber';
const itemList = [
@@ -130,6 +130,60 @@ export default function Index() {
};
/* -------------------------------------------------------------------------- */
/* ------------------------------ handle search ----------------------------- */
const [searchText, setSearchText] = useState('');
const handleSearchSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (searchText === '') {
searchParams.delete('search');
const params = Object.fromEntries([...searchParams.entries()]);
setAppliedParams(params);
} else {
const params = Object.fromEntries([...searchParams.entries(), ['search', searchText]]);
setAppliedParams(params);
}
};
const searchs = {
searchText: searchText,
setSearchText: setSearchText,
handleSearchSubmit: handleSearchSubmit,
};
/* -------------------------------------------------------------------------- */
/* ------------------------------ handle filter ----------------------------- */
const [divisionValue, setDivisionValue] = useState('all');
const [divisionData, setDivisionData] = useState([]);
const handleDivisionChange = (event: SelectChangeEvent) => {
setDivisionValue(event.target.value as string);
if (event.target.value === 'all') {
searchParams.delete('division');
const params = Object.fromEntries([...searchParams.entries()]);
setAppliedParams(params);
} else {
const params = Object.fromEntries([
...searchParams.entries(),
['division', event.target.value as string],
]);
setAppliedParams(params);
}
};
const filters = {
useFilter: true,
config: {
label: 'Division',
divisionValue: divisionValue,
divisionData: divisionData,
handleDivisionChange: handleDivisionChange,
},
};
/* -------------------------------------------------------------------------- */
/* -------------------------------- headCell -------------------------------- */
const headCells: HeadCell<never>[] = [
{
@@ -181,6 +235,7 @@ export default function Index() {
useEffect(() => {
(async () => {
setIsLoading(true);
let mounted = true;
await new Promise((resolve) => setTimeout(resolve, 250));
@@ -196,17 +251,30 @@ export default function Index() {
params: { ...parameters },
});
setPolicyData(corporatePolicyLimit.data.data);
setSearchParams(parameters);
setMemberData(corporateMembers.data.data);
setPaginationTable(corporateMembers.data);
setRowsPerPage(corporateMembers.data.per_page);
setIsLoading(false);
if (mounted) {
setPolicyData(corporatePolicyLimit.data.data);
setDivisionData(corporateDivision.data);
setMemberData(corporateMembers.data.data);
setPaginationTable(corporateMembers.data);
setRowsPerPage(corporateMembers.data.per_page);
setIsLoading(false);
}
return () => {
mounted = false;
};
})();
}, [appliedParams, searchParams, order, orderBy, setSearchParams, corporateValue]);
/* ------------------------------- card policy ------------------------------ */
const newPolicyData = {
limit: policyData,
};
/* -------------------------------------------------------------------------- */
const newMemberData: any = memberData.map((obj: any) => {
return {
...obj,
@@ -270,7 +338,7 @@ export default function Index() {
<CardNotification data={itemList} />
</Grid>
<Grid item xs={12} lg={6} md={12}>
<CardPolicy data={policyData} />
<CardPolicy data={newPolicyData} />
</Grid>
<Grid item xs={12} lg={12} md={12}>
<Table
@@ -280,8 +348,9 @@ export default function Index() {
paginations={paginations}
loadings={loadings}
params={params}
searchs={searchs}
filters={filters}
/>
{/* <TableList /> */}
</Grid>
</Grid>
</Container>

View File

@@ -22,15 +22,26 @@ import DialogClaimSubmitMember from './DialogClaimSubmitMember';
type CardPolicyProps = {
data: {
myLimit: {
balance: number;
total: number;
percentage: number;
};
lockLimit: {
balance: number;
percentage: number;
limit: {
myLimit: {
balance: number;
total: number;
percentage: number;
};
lockLimit: {
balance: number;
percentage: number;
};
};
// topUpLimit: {
// companyName: string;
// policyNumber: number;
// totalMembers: number;
// totalCases: number;
// totalPersen: number;
// myLimit: number;
// totalLimit: number;
// };
};
};
@@ -65,7 +76,7 @@ export default function CardPolicy(props: CardPolicyProps) {
const [dialogTitle, setDialogTitle] = useState('');
const [isDialog, setIsDialog] = useState('');
const { myLimit, lockLimit } = props.data;
const { limit } = props.data;
const clickHandler = (isDialog: string) => {
switch (isDialog) {
@@ -94,23 +105,23 @@ export default function CardPolicy(props: CardPolicyProps) {
Total Limit
</Typography>
<Typography sx={{ typography: 'body2' }}>
{fCurrency(myLimit ? myLimit.balance : 0)}
{fCurrency(limit.myLimit ? limit.myLimit.balance : 0)}
</Typography>
<Typography sx={{ typography: 'caption', color: '#919EAB' }}>
/ {fSplit(myLimit ? myLimit.total : 0)}
/ {fSplit(limit.myLimit ? limit.myLimit.total : 0)}
</Typography>
</div>
<Stack direction="row" alignItems="center" justifyContent="center">
<Typography variant="h5" sx={{ ml: 0.5 }}>
{myLimit ? myLimit.percentage : 0}%
{limit.myLimit ? limit.myLimit.percentage : 0}%
</Typography>
</Stack>
</Stack>
<BorderLinearProgress
variant="determinate"
value={myLimit ? myLimit.percentage : 0}
value={limit.myLimit ? limit.myLimit.percentage : 0}
sx={{ mb: 1 }}
/>
@@ -123,11 +134,12 @@ export default function CardPolicy(props: CardPolicyProps) {
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Lock Fund ( {lockLimit ? lockLimit.percentage : 0}% )
Lock Fund ( {limit.lockLimit ? limit.lockLimit.percentage : 0}% )
</Typography>
</Typography>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
{fSplit(lockLimit ? lockLimit.balance : 0)} / {fSplit(myLimit ? myLimit.total : 0)}
{fSplit(limit.lockLimit ? limit.lockLimit.balance : 0)} /{' '}
{fSplit(limit.myLimit ? limit.myLimit.total : 0)}
</Typography>
</Stack>

View File

@@ -21,14 +21,7 @@ import * as Yup from 'yup';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
// ----------------------------------------------------------------------
type DataContent = {
info: string;
date: string;
time: string;
};
/* ---------------------------------- types --------------------------------- */
type MuiDialogProps = {
title?: {
name?: string;
@@ -37,24 +30,31 @@ type MuiDialogProps = {
openDialog: boolean;
setOpenDialog: Function;
content?: ReactElement;
data?: DataContent[];
data?: {
companyName: string;
policyNumber: number;
totalMembers: number;
totalCases: number;
totalPersen: number;
myLimit: number;
totalLimit: number;
};
};
type FormValuesProps = {
topup: string;
};
/* -------------------------------------------------------------------------- */
// ----------------------------------------------------------------------
const testData = {
companyName: 'PT. Aman Mineral',
policyNumber: 12345678,
totalMembers: 50,
totalCases: 100,
totalPersen: 75,
myLimit: 375000000,
totalLimit: 500000000,
};
// const testData = {
// companyName: 'PT. Aman Mineral',
// policyNumber: 12345678,
// totalMembers: 50,
// totalCases: 100,
// totalPersen: 75,
// myLimit: 375000000,
// totalLimit: 500000000,
// };
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
@@ -70,7 +70,12 @@ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
// ----------------------------------------------------------------------
const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogProps) => {
export default function DialogTopUpLimit({
title,
openDialog,
setOpenDialog,
data,
}: MuiDialogProps) {
const [isDisabledCheckbox, setIsDisabledCheckbox] = useState(false);
const [isDisabledButton, setIsDisabledButton] = useState(true);
@@ -126,42 +131,42 @@ const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogP
<Typography variant="caption" color="#637381">
Company Name
</Typography>
<Typography variant="body2">{testData.companyName}</Typography>
<Typography variant="body2">{data ? data.companyName : ''}</Typography>
</Stack>
<Stack>
<Typography variant="caption" color="#637381">
Policy Number
</Typography>
<Typography variant="body2">{testData.policyNumber}</Typography>
<Typography variant="body2">{data ? data.policyNumber : 0}</Typography>
</Stack>
<Stack direction="row" spacing={22}>
<Stack>
<Typography variant="caption" color="#637381">
Total Member
</Typography>
<Typography variant="body2">{testData.totalMembers} Person</Typography>
<Typography variant="body2">{data ? data.totalMembers : 0} Person</Typography>
</Stack>
<Stack>
<Typography variant="caption" color="#637381">
Total Cases
</Typography>
<Typography variant="body2">{testData.totalCases} Cases</Typography>
<Typography variant="body2">{data ? data.totalCases : 0} Cases</Typography>
</Stack>
</Stack>
<Stack spacing={1} sx={{ backgroundColor: '#F4F6F8', borderRadius: 1.5, padding: 2 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack>
<Typography variant="body2">Company Pooled Fund</Typography>
<Typography variant="body2">{fCurrency(testData.myLimit)}</Typography>
<Typography variant="body2">{fCurrency(data ? data.myLimit : 0)}</Typography>
<Typography variant="caption" color="#919EAB">
/ {testData.totalLimit}
/ {data ? data.totalLimit : 0}
</Typography>
</Stack>
<Stack>
<Typography variant="h5">{testData.totalPersen}%</Typography>
<Typography variant="h5">{data ? data.totalPersen : 0}%</Typography>
</Stack>
</Stack>
<BorderLinearProgress variant="determinate" value={testData.totalPersen} />
<BorderLinearProgress variant="determinate" value={data ? data.totalPersen : 0} />
</Stack>
<Stack spacing={2}>
<Typography variant="subtitle1" marginTop={3}>
@@ -178,7 +183,7 @@ const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogP
<FormControlLabel
sx={{ typography: 'caption' }}
control={<Checkbox />}
label={'Max ' + fCurrency(testData.totalLimit - testData.myLimit)}
label={'Max ' + fCurrency((data ? data.totalLimit : 0) - (data ? data.myLimit : 0))}
onChange={handleSubmit(onCheckHandler)}
/>
@@ -207,6 +212,4 @@ const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogP
maxWidth="xs"
/>
);
};
export default DialogTopUpLimit;
}