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

This commit is contained in:
Fajar
2023-03-24 09:22:32 +07:00
20 changed files with 2567 additions and 2754 deletions

View File

@@ -3,49 +3,25 @@
namespace Modules\Client\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Models\Member;
use App\Services\CorporateMemberService;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Client\Transformers\MemberResources;
use Modules\Client\Transformers\Dashboard\MemberResources;
class CorporateMemberController extends Controller
{
public function __construct(public CorporateMemberService $corporateMemberService)
{
}
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index(Request $request, $corporate_id)
{
$limit = $request->has('per_page') ? $request->per_page : 10;
$members = Member::query()
->whereHas('employeds', function ($corporateEmployee) use ($corporate_id) {
$corporateEmployee->where('corporate_id', $corporate_id);
})->when($request->input('search'), function ($query, $search) {
$query->where('member_id', 'like', "%" . $search . "%")
->orWhere('name', 'like', "%" . $search . "%");
});
if ($request->input('claimMember') === 'false') {
$members = $members->when($request->input('division'), function ($division, $division_id) {
$division->whereHas('division', function ($corporateEmployee) use ($division_id) {
$corporateEmployee->where('division_id', $division_id);
});
})->when($request->has('orderBy'), function ($query) use ($request) {
$query->orderBy($request->orderBy, $request->order);
});
}
// else {
// $members = $members->get();
// return response()->json(MemberResources::collection($members));
// }
$members->with('currentPlan');
$members->withSum('claims', 'total_claim');
$members = $members->paginate($limit);
// return $members;
$members = $this->corporateMemberService->getAllDashboardMembers($corporate_id, $request);
return response()->json(Helper::paginateResources(MemberResources::collection($members)));
}

View File

@@ -2,11 +2,12 @@
namespace Modules\Client\Http\Controllers\Api;
use App\Helpers\Helper;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Modules\Client\Transformers\DashboardResources;
use Modules\Client\Transformers\Dashboard\LimitResources;
class CorporatePolicyController extends Controller
{
@@ -16,14 +17,13 @@ class CorporatePolicyController extends Controller
*/
public function index(Request $request, $corporate_id)
{
$user = Auth::user();
$currentCorporate = $user->managedCorporates()
$currentCorporate = Auth::user()->managedCorporates()
->with(['currentPolicy', 'employees'])
->find($corporate_id);
$data = DashboardResources::make($currentCorporate);
$data = LimitResources::make($currentCorporate);
return response()->json($data);
return Helper::responseJson($data);
}
/**

View File

@@ -1,11 +1,10 @@
<?php
namespace Modules\Client\Transformers;
namespace Modules\Client\Transformers\Dashboard;
use App\Helpers\Helper;
use Illuminate\Http\Resources\Json\JsonResource;
class DashboardResources extends JsonResource
class LimitResources extends JsonResource
{
/**
* Transform the resource into an array.
@@ -22,17 +21,15 @@ class DashboardResources extends JsonResource
$lockPercentage = (int)$this->currentPolicy->minimal_stop_service_percentage;
return [
'policy' => [
'myLimit' => [
'balance' => $myLimitBalance,
'total' => $myLimitTotal,
'percentage' => $myLimitTotal ? (($myLimitBalance / $myLimitTotal) * 100) : 0,
],
'lockLimit' => [
'balance' => $lockBalance,
'percentage' => $lockPercentage
]
'myLimit' => [
'balance' => $myLimitBalance,
'total' => $myLimitTotal,
'percentage' => $myLimitTotal ? round(($myLimitBalance / $myLimitTotal) * 100, 2) : 0,
],
'lockLimit' => [
'balance' => $lockBalance,
'percentage' => $lockPercentage
]
];
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace Modules\Client\Transformers;
namespace Modules\Client\Transformers\Dashboard;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -18,15 +18,13 @@ class MemberResources extends JsonResource
'id' => $this->id,
'memberId' => $this->member_id,
'fullName' => $this->full_name,
$this->mergeWhen($request->input('claimMember') === 'false', [
'division' => $this->division->name ?? '',
'status' => $this->active
]),
'division' => $this->division_name ?? '',
'limit' => [
'current' => $this->claims_sum_total_claim,
'total' => $this->currentPlan->limit_rules ?? 0,
'percentage' => (!empty($this->currentPlan->limit_rules ?? 0)) ? (($this->claims_sum_total_claim / $this->currentPlan->limit_rules) * 100) : 0
],
'status' => $this->active,
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Services;
use App\Models\Member;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class CorporateMemberService
{
public function getAllDashboardMembers(int $corporateId, Request $request)
{
$limit = $request->has('perPage') ? $request->input('perPage') : 10;
return Member::query()
->select(['members.id', 'members.person_id', 'members.member_id', 'members.name', 'members.name_prefix', 'members.name_suffix', 'corporate_divisions.name AS division_name', 'members.active'])
->leftJoin('corporate_employees', 'members.id', '=', 'corporate_employees.member_id')
->leftJoin('corporate_divisions', 'corporate_employees.division_id', '=', 'corporate_divisions.id')
->with('currentPlan')
->withSum('claims', 'total_claim')
->whereHas('employeds', function (Builder $corporateEmployee) use ($corporateId) {
$corporateEmployee->where('corporate_id', $corporateId);
})
->when($request->input('search'), function (Builder $query, $search) {
$query->where('member_id', 'like', "%" . $search . "%")
->orWhere('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->has('orderBy'), function (Builder $query) use ($request) {
$orderBy = match ($request->orderBy) {
'memberId' => 'member_id',
'fullName' => 'name',
'status' => 'active',
default => ''
};
if (in_array($orderBy, ['member_id', 'name', 'active'])) {
$query->getQuery()->orderBy($orderBy, $request->order);
} elseif ($request->orderBy === 'division') {
$query->getQuery()->orderBy('corporate_divisions.name', $request->order);
}
})
->paginate($limit);
}
}

View File

@@ -56,7 +56,7 @@ return [
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'strict' => env('DB_STRICT', true),
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
@@ -145,7 +145,7 @@ return [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [

View File

@@ -37,80 +37,81 @@
]
},
"dependencies": {
"@date-io/date-fns": "^2.15.0",
"@emotion/cache": "^11.10.3",
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@hookform/resolvers": "^2.9.7",
"@date-io/date-fns": "^2.16.0",
"@emotion/cache": "^11.10.5",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@hookform/resolvers": "^2.9.11",
"@iconify/react": "^3.2.2",
"@mui/icons-material": "^5.10.2",
"@mui/icons-material": "^5.11.11",
"@mui/lab": "5.0.0-alpha.80",
"@mui/material": "^5.10.2",
"@mui/system": "^5.10.2",
"@mui/utils": "^5.10.16",
"@mui/x-data-grid": "^5.16.0",
"@mui/material": "^5.11.14",
"@mui/system": "^5.11.14",
"@mui/utils": "^5.11.13",
"@mui/x-data-grid": "^5.17.26",
"@mui/x-date-pickers": "5.0.0-beta.2",
"@vitejs/plugin-react": "^1.3.2",
"apexcharts": "^3.36.3",
"apexcharts": "^3.37.2",
"axios": "^0.27.2",
"change-case": "^4.1.2",
"csstype": "^3.1.0",
"date-fns": "^2.29.2",
"csstype": "^3.1.1",
"date-fns": "^2.29.3",
"framer-motion": "^6.5.1",
"highlight.js": "^11.6.0",
"highlight.js": "^11.7.0",
"history": "^5.3.0",
"jsx-runtime": "^1.2.0",
"lodash": "^4.17.21",
"notistack": "^3.0.0-alpha.7",
"notistack": "^3.0.1",
"nprogress": "^0.2.0",
"numeral": "^2.0.6",
"pusher-js": "^8.0.2",
"react": "^17.0.2",
"react-apexcharts": "^1.4.0",
"react-dom": "^17.0.2",
"react-dropzone": "^14.2.2",
"react-dropzone": "^14.2.3",
"react-helmet-async": "^1.3.0",
"react-hook-form": "^7.34.2",
"react-hook-form": "^7.43.7",
"react-intersection-observer": "^8.34.0",
"react-lazy-load-image-component": "^1.5.5",
"react-number-format": "^5.1.1",
"react-lazy-load-image-component": "^1.5.6",
"react-number-format": "^5.1.4",
"react-quill": "2.0.0-beta.4",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"simplebar": "^5.3.8",
"simplebar-react": "^2.4.1",
"stylis": "^4.1.1",
"react-router": "^6.9.0",
"react-router-dom": "^6.9.0",
"simplebar": "^5.3.9",
"simplebar-react": "^2.4.3",
"stylis": "^4.1.3",
"stylis-plugin-rtl": "^2.1.1",
"vite": "^3.0.9",
"vite-plugin-svgr": "^2.2.1",
"vite": "^3.2.5",
"vite-plugin-svgr": "^2.4.0",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/eslint-parser": "^7.18.9",
"@babel/core": "^7.21.3",
"@babel/eslint-parser": "^7.21.3",
"@babel/plugin-syntax-flow": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.18.10",
"@types/lodash": "^4.14.184",
"@babel/plugin-transform-react-jsx": "^7.21.0",
"@types/lodash": "^4.14.191",
"@types/nprogress": "^0.2.0",
"@types/react": "^17.0.48",
"@types/react-dom": "^17.0.17",
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.19",
"@types/react-lazy-load-image-component": "^1.5.2",
"@types/stylis": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^5.35.1",
"@typescript-eslint/parser": "^5.35.1",
"eslint": "^8.22.0",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"eslint": "^8.36.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "^16.2.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-react-app": "7.0.0",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "4.3.0",
"prettier": "^2.7.1",
"typescript": "^4.8.2",
"vite-plugin-pwa": "^0.12.3"
"prettier": "^2.8.6",
"typescript": "^4.9.5",
"vite-plugin-pwa": "^0.12.8"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
export type PolicyProps = {
myLimit: {
balance: number;
total: number;
percentage: number;
};
lockLimit: {
balance: number;
percentage: number;
};
};

View File

@@ -0,0 +1,66 @@
import { Dispatch, SetStateAction } from 'react';
/* ------------------------------- pagination ------------------------------- */
export type PaginationTableProps = {
current_page: number;
from: number;
last_page: number;
links: [];
path: string;
per_page: number;
to: number;
total: number;
};
/* -------------------------------------------------------------------------- */
/* ---------------------------------- order --------------------------------- */
export type Order = 'asc' | 'desc';
/* -------------------------------------------------------------------------- */
/* --------------------------------- filter --------------------------------- */
export type DivisionDataProps = {
id: number;
name: string;
};
/* -------------------------------------------------------------------------- */
/* -------------------------------- headcell -------------------------------- */
export type HeadCell<DataType> = {
id: Extract<keyof DataType, string>;
align: string;
label: string;
isSort: boolean;
width?: number;
};
/* -------------------------------------------------------------------------- */
/* ----------------------------------- row ---------------------------------- */
export type TableListProps<DataType> = {
headCells?: HeadCell<DataType>[];
rows?: Array<DataType>;
paginations: {
page: number;
setPage: Dispatch<SetStateAction<number>>;
rowsPerPage: number;
setRowsPerPage: Dispatch<SetStateAction<number>>;
paginationTable: PaginationTableProps;
setPaginationTable: Dispatch<SetStateAction<PaginationTableProps>>;
};
orders: {
order: Order;
setOrder: Dispatch<SetStateAction<Order>>;
orderBy: string;
setOrderBy: Dispatch<SetStateAction<string>>;
};
loadings: {
isLoading: boolean;
setIsLoading: Dispatch<SetStateAction<boolean>>;
};
params: {
searchParams: URLSearchParams;
setSearchParams: any;
appliedParams: {};
setAppliedParams: Dispatch<SetStateAction<{}>>;
};
};
/* -------------------------------------------------------------------------- */

View File

@@ -0,0 +1,289 @@
/* ---------------------------------- @mui ---------------------------------- */
import { styled } from '@mui/material/styles';
import {
Paper,
Table as TableContent,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Button,
TableSortLabel,
Box,
Card,
Grid,
FormControl,
InputLabel,
Select,
MenuItem,
SelectChangeEvent,
Stack,
Typography,
LinearProgress,
linearProgressClasses,
} from '@mui/material';
import { visuallyHidden } from '@mui/utils';
/* ---------------------------------- axios --------------------------------- */
import axios from '../utils/axios';
/* ---------------------------------- react --------------------------------- */
import { useContext, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
/* -------------------------------- component ------------------------------- */
import BaseTablePagination from './BaseTablePagination';
/* ---------------------------------- theme --------------------------------- */
import palette from '../theme/palette';
/* ---------------------------------- utils --------------------------------- */
import { UserCurrentCorporateContext } from '../contexts/UserCurrentCorporate';
import { fSplit } from '../utils/formatNumber';
/* ---------------------------------- types --------------------------------- */
import { DivisionDataProps, Order, PaginationTableProps, TableListProps } from '../@types/table';
/* --------------------------------- styled --------------------------------- */
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 6,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: '#D1F1F1',
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 6,
backgroundColor: theme.palette.primary.main,
},
}));
/* -------------------------------------------------------------------------- */
export default function Table<T>({
headCells,
rows,
paginations,
orders,
loadings,
params,
}: TableListProps<T>) {
/* ------------------------------- handle sort ------------------------------ */
const handleRequestSort = async (event: React.MouseEvent<unknown>, property: string) => {
const isAsc = orders?.orderBy === property && orders?.order === 'asc';
orders?.setOrder(isAsc ? 'desc' : 'asc');
orders?.setOrderBy(property);
const parameters = Object.fromEntries([
...params.searchParams.entries(),
['order', isAsc ? 'desc' : 'asc'],
['orderBy', property],
]);
params.setAppliedParams(parameters);
};
/* -------------------------------------------------------------------------- */
/* -------------------------- enchanced table head -------------------------- */
const EnhancedTableHead = () => {
const createSortHandler = (property: string) => (event: React.MouseEvent<unknown>) => {
handleRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
{headCells &&
headCells.map((headCell, index) => (
<TableCell
key={index}
sortDirection={orders?.orderBy === headCell.id ? orders.order : false}
// @ts-ignore
align={headCell.align}
sx={{ padding: 2 }}
width={headCell.width ? headCell.width : 'auto'}
>
{headCell.isSort ? (
<TableSortLabel
active={orders?.orderBy === headCell.id}
direction={orders?.orderBy === headCell.id ? orders.order : 'asc'}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orders?.orderBy === headCell.id ? (
<Box component="span" sx={visuallyHidden}>
{orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</Box>
) : null}
</TableSortLabel>
) : (
headCell.label
)}
</TableCell>
))}
</TableRow>
</TableHead>
);
};
/* -------------------------------------------------------------------------- */
/* ----------------------------- 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,
newPage: number
) => {
const parameters = Object.fromEntries([
...params.searchParams.entries(),
['page', newPage + 1],
]);
paginations.setPage(newPage);
await new Promise((resolve) => setTimeout(resolve, 500));
params.setAppliedParams(parameters);
};
/* -------------------------------------------------------------------------- */
/* --------------------------- row page per limit --------------------------- */
const onRowsPerPageChangeHandle = async (event: React.ChangeEvent<HTMLInputElement>) => {
params.searchParams.delete('page');
const parameters = Object.fromEntries([
...params.searchParams.entries(),
['per_page', parseInt(event.target.value, 10)],
]);
paginations.setPage(0);
paginations.setRowsPerPage(parseInt(event.target.value, 10));
await new Promise((resolve) => setTimeout(resolve, 500));
params.setAppliedParams(parameters);
};
/* -------------------------------------------------------------------------- */
return (
<Card>
<Grid container>
{/* Field 1 */}
{/* <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>
</Grid>
</Grid> */}
{/* End Field 1 */}
{/* Field 2 */}
<Grid item xs={12}>
{/* Table */}
<TableContainer component={Paper}>
<TableContent aria-label="collapsible table" size="small">
{/* Table Header */}
<EnhancedTableHead />
{/* End Table Header */}
{/* Table Body */}
<TableBody>
{loadings.isLoading ? (
<TableRow>
<TableCell colSpan={headCells?.length} align="center">
Loading . . .
</TableCell>
</TableRow>
) : rows && rows.length >= 1 ? (
rows.map((row, rowIndex) => (
<TableRow key={rowIndex}>
{headCells &&
//@ts-ignore
headCells.map((head, headIndex) => (
//@ts-ignore
<TableCell align={head.align} key={headIndex}>
{row[head.id]}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} align="center">
No Data Found
</TableCell>
</TableRow>
)}
</TableBody>
{/* End Table Body */}
</TableContent>
</TableContainer>
{/* End Table */}
{/* Pagination */}
<BaseTablePagination
count={paginations.paginationTable.total}
onPageChange={onPageChangeHandle}
page={paginations.page}
rowsPerPage={paginations.rowsPerPage}
onRowsPerPageChange={onRowsPerPageChangeHandle}
/>
{/* End Pagination */}
</Grid>
{/* End Field 2 */}
</Grid>
</Card>
);
}

View File

@@ -1,93 +0,0 @@
// @mui
import { Typography, Container, Grid } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
// components
import Page from '../../components/Page';
// theme
import CardNotification from '../../sections/dashboard/CardNotification';
import CardBalance from '../../sections/dashboard/CardBalance';
import TableList from '../../sections/dashboard/TableList';
import { useContext, useEffect, useState } from 'react';
import axios from '../../utils/axios';
import { Stack } from '@mui/system';
import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate';
// ----------------------------------------------------------------------
const itemList = [
{ info: 'Mohon lengkapi dokumen Alison Born', date: 'Selasa, 13 Februari 23', time: '09:43 WIB' },
];
// ----------------------------------------------------------------------
/* ---------------------------------- types --------------------------------- */
type PolicyProps = {
myLimit: {
balance: number;
total: number;
percentage: number;
};
lockLimit: {
balance: number;
percentage: number;
};
};
/* -------------------------------------------------------------------------- */
/* ------------------------------ default data ------------------------------ */
const defaultPolicyData = {
myLimit: {
balance: 0,
total: 0,
percentage: 0,
},
lockLimit: {
balance: 0,
percentage: 0,
},
};
/* -------------------------------------------------------------------------- */
export default function Dashboard() {
const { themeStretch } = useSettings();
const { corporateValue } = useContext(UserCurrentCorporateContext);
// const [tableData, setTableData] = useState([]);
const [policyData, setPolicyData] = useState<PolicyProps>(defaultPolicyData);
useEffect(() => {
(async () => {
setPolicyData(defaultPolicyData);
await new Promise((resolve) => setTimeout(resolve, 250));
const dashboard = await axios.get(`${corporateValue}/policy`);
setPolicyData(dashboard.data.policy);
})();
}, [corporateValue]);
return (
<Page title="Dashboard">
<Container maxWidth={themeStretch ? false : 'xl'}>
<Stack direction="row" justifyContent="space-between">
<Typography variant="h3" component="h1" paragraph>
Dashboard
</Typography>
</Stack>
<Grid container spacing={2}>
<Grid item xs={12} lg={6} md={12}>
<CardNotification data={itemList} />
</Grid>
<Grid item xs={12} lg={6} md={12}>
<CardBalance data={policyData} />
</Grid>
<Grid item xs={12} lg={12} md={12}>
<TableList />
</Grid>
</Grid>
</Container>
</Page>
);
}

View File

@@ -0,0 +1,290 @@
// @mui
import { styled } from '@mui/material/styles';
import {
Typography,
Container,
Grid,
Button,
IconButton,
LinearProgress,
linearProgressClasses,
} from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
// components
import Page from '../../components/Page';
// theme
import CardNotification from '../../sections/dashboard/CardNotification';
import CardPolicy from '../../sections/dashboard/CardPolicy';
import { useContext, useEffect, useState } from 'react';
import axios from '../../utils/axios';
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 { 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 = [
{ info: 'Mohon lengkapi dokumen Alison Born', date: 'Selasa, 13 Februari 23', time: '09:43 WIB' },
{ info: 'Mohon lengkapi dokumen Alison Born', date: 'Selasa, 13 Februari 23', time: '09:43 WIB' },
{ info: 'Mohon lengkapi dokumen Alison Born', date: 'Selasa, 13 Februari 23', time: '09:43 WIB' },
{ info: 'Mohon lengkapi dokumen Alison Born', date: 'Selasa, 13 Februari 23', time: '09:43 WIB' },
];
/* ------------------------------ default data ------------------------------ */
const defaultPolicyData = {
myLimit: {
balance: 0,
total: 0,
percentage: 0,
},
lockLimit: {
balance: 0,
percentage: 0,
},
};
/* -------------------------------------------------------------------------- */
export default function Index() {
const { themeStretch } = useSettings();
const { corporateValue } = useContext(UserCurrentCorporateContext);
const [memberData, setMemberData] = useState([]);
const [policyData, setPolicyData] = useState<PolicyProps>(defaultPolicyData);
/* -------------------------------------------------------------------------- */
/* setting up for the table */
/* -------------------------------------------------------------------------- */
const [isLoading, setIsLoading] = useState(true);
const loadings = {
isLoading: isLoading,
setIsLoading: setIsLoading,
};
/* ----------------------------- limit progress ----------------------------- */
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 6,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: '#D1F1F1',
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 6,
backgroundColor: theme.palette.primary.main,
},
}));
/* -------------------------------------------------------------------------- */
/* ------------------------------ handle params ----------------------------- */
const [searchParams, setSearchParams] = useSearchParams();
const [appliedParams, setAppliedParams] = useState({});
const params = {
searchParams: searchParams,
setSearchParams: setSearchParams,
appliedParams: appliedParams,
setAppliedParams: setAppliedParams,
};
/* -------------------------------------------------------------------------- */
/* ------------------------------ handle order ------------------------------ */
const [order, setOrder] = useState<Order>('asc');
const [orderBy, setOrderBy] = useState('fullName');
const orders = {
order: order,
setOrder: setOrder,
orderBy: orderBy,
setOrderBy: setOrderBy,
};
/* -------------------------------------------------------------------------- */
/* ---------------------------- handle pagination --------------------------- */
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [paginationTable, setPaginationTable] = useState<PaginationTableProps>({
current_page: 0,
from: 0,
last_page: 0,
links: [],
path: '',
per_page: 0,
to: 0,
total: 0,
});
const paginations = {
page: page,
setPage: setPage,
rowsPerPage: rowsPerPage,
setRowsPerPage: setRowsPerPage,
paginationTable: paginationTable,
setPaginationTable: setPaginationTable,
};
/* -------------------------------------------------------------------------- */
/* -------------------------------- headCell -------------------------------- */
const headCells: HeadCell<never>[] = [
{
id: 'memberId',
align: 'left',
label: 'Member ID',
isSort: true,
},
{
id: 'fullName',
align: 'center',
label: 'Name',
isSort: true,
},
{
id: 'division',
align: 'center',
label: 'Divisi',
isSort: true,
},
{
id: 'limit',
align: 'center',
label: 'Limit',
isSort: false,
// width: 170,
},
{
id: 'status',
align: 'center',
label: 'Status',
isSort: true,
},
{
id: 'action',
align: 'right',
label: '',
isSort: false,
},
];
/* -------------------------------------------------------------------------- */
/* ----------------------------- handler action ----------------------------- */
const handleAction = () => {
alert('action');
};
/* -------------------------------------------------------------------------- */
useEffect(() => {
(async () => {
setIsLoading(true);
await new Promise((resolve) => setTimeout(resolve, 250));
const parameters =
Object.keys(appliedParams).length !== 0
? appliedParams
: Object.fromEntries([...searchParams.entries(), ['order', order], ['orderBy', orderBy]]);
// get data policy, division, member using axios
const corporatePolicyLimit = await axios.get(`${corporateValue}/policy`);
const corporateDivision = await axios.get(`${corporateValue}/division`);
const corporateMembers = await axios.get(`${corporateValue}/members`, {
params: { ...parameters },
});
setPolicyData(corporatePolicyLimit.data.data);
setSearchParams(parameters);
setMemberData(corporateMembers.data.data);
setPaginationTable(corporateMembers.data);
setRowsPerPage(corporateMembers.data.per_page);
setIsLoading(false);
})();
}, [appliedParams, searchParams, order, orderBy, setSearchParams, corporateValue]);
const newMemberData: any = memberData.map((obj: any) => {
return {
...obj,
limit: (
<Stack>
<BorderLinearProgress variant="determinate" value={obj.limit.percentage} sx={{ mb: 1 }} />
<Typography sx={{ typography: 'caption', color: '#637381' }}>
{fSplit(obj.limit.current)} / {fSplit(obj.limit.total)}
</Typography>
</Stack>
),
status:
obj.status === 1 ? (
<Button
sx={{
backgroundColor: 'rgba(84, 214, 44, 0.16)',
color: palette.dark.success.dark,
paddingY: 0,
'&:hover': {
backgroundColor: 'rgba(84, 214, 44, 0.32)',
color: palette.dark.success.darker,
},
}}
>
Active
</Button>
) : (
<Button
sx={{
backgroundColor: 'rgba(255, 72, 66, 0.16)',
color: palette.dark.error.dark,
paddingY: 0,
'&:hover': {
backgroundColor: 'rgba(255, 72, 66, 0.32)',
color: palette.dark.error.darker,
},
}}
>
Inactive
</Button>
),
action: (
<IconButton onClick={handleAction}>
<MoreVertIcon />
</IconButton>
),
};
});
return (
<Page title="Dashboard">
<Container maxWidth={themeStretch ? false : 'xl'}>
<Stack direction="row" justifyContent="space-between">
<Typography variant="h3" component="h1" paragraph>
Dashboard
</Typography>
</Stack>
<Grid container spacing={2}>
<Grid item xs={12} lg={6} md={12}>
<CardNotification data={itemList} />
</Grid>
<Grid item xs={12} lg={6} md={12}>
<CardPolicy data={policyData} />
</Grid>
<Grid item xs={12} lg={12} md={12}>
<Table
headCells={headCells}
rows={newMemberData}
orders={orders}
paginations={paginations}
loadings={loadings}
params={params}
/>
{/* <TableList /> */}
</Grid>
</Grid>
</Container>
</Page>
);
}

View File

@@ -144,7 +144,7 @@ export default function Router() {
const Login = Loadable(lazy(() => import('../pages/auth/Login')));
// Dashboard
const Dashboard = Loadable(lazy(() => import('../pages/Dashboard/Dashboard')));
const Dashboard = Loadable(lazy(() => import('../pages/Dashboard/Index')));
const NotFound = Loadable(lazy(() => import('../pages/Page404')));
// Alarm Center

View File

@@ -1,103 +0,0 @@
// @mui
import { styled } from '@mui/material/styles';
import {
Button,
Card,
Typography,
Stack,
LinearProgress,
linearProgressClasses,
} from '@mui/material';
// utils
import { fCurrency } from '../../utils/formatNumber';
// components
import Iconify from '../../components/Iconify';
// ----------------------------------------------------------------------
const RootStyle = styled(Card)(({ theme }) => ({
boxShadow: 'none',
padding: theme.spacing(3),
color: 'black',
backgroundColor: theme.palette.grey[200],
maxHeight: '240px',
}));
// ----------------------------------------------------------------------
const INITIAL = '500.000.000';
const TOTAL = 375000000;
const PERCENT = 75;
export default function BalanceCard(props: any) {
const { setOpenPopup } = props;
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,
},
}));
return (
<RootStyle>
<Stack direction="row" justifyContent="space-between" sx={{ mb: 1 }}>
<div>
<Typography variant="body2" component="span" sx={{ opacity: 0.72 }}>
Total Limit
</Typography>
<Typography sx={{ typography: 'body2' }}>{fCurrency(TOTAL)}</Typography>
<Typography sx={{ typography: 'caption', color: '#919EAB' }}>/ {INITIAL}</Typography>
</div>
<Stack direction="row" alignItems="center" justifyContent="center">
<Typography variant="h5" sx={{ ml: 0.5 }}>
{PERCENT}%
</Typography>
</Stack>
</Stack>
<BorderLinearProgress variant="determinate" value={PERCENT} sx={{ mb: 1 }} />
<Stack sx={{ backgroundColor: '#B2E8E8', paddingY: 1, paddingX: 1.5, mb: 2 }}>
<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}>
<Button
variant="outlined"
startIcon={<Iconify icon="bi:clipboard-check-fill" />}
fullWidth={true}
onClick={() => setOpenPopup(true)}
>
Submit Claim
</Button>
<Button
variant="contained"
startIcon={<Iconify icon="heroicons-solid:cash" />}
fullWidth={true}
>
Top Up
</Button>
</Stack>
</RootStyle>
);
}

View File

@@ -26,9 +26,10 @@ type NotificationProps = {
const RootNotificationStyle = styled(Card)(({ theme }) => ({
boxShadow: 'none',
padding: '1rem 0.5rem',
padding: '1.5rem',
color: 'black',
backgroundColor: theme.palette.grey[200],
height: '100%',
maxHeight: '240px',
}));
@@ -37,6 +38,10 @@ const ItemNotificationStyle = styled(Card)(({ theme }) => ({
padding: theme.spacing(1),
borderRadius: 0.5,
color: 'black',
marginTop: 2,
overflowY: 'auto',
maxHeight: '154px',
gap: '0.5rem',
}));
// ----------------------------------------------------------------------
@@ -95,11 +100,11 @@ export default function CardNotification({ data }: NotificationProps) {
</Button>
</Stack>
<ItemNotificationStyle sx={{ marginTop: 2, overflowY: 'auto', maxHeight: '154px' }}>
<ItemNotificationStyle>
{data
? data.map(({ info, date, time }, key) => (
<div key={key}>
{key >= 1 ? <Divider sx={{ marginY: 0.5 }} /> : ''}
? data.map(({ info, date, time }, index) => (
<div key={index}>
{index >= 1 ? <Divider sx={{ marginY: 0.5 }} /> : ''}
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="column" justifyContent="flex-start" alignItems="flex-start">
<Typography sx={{ typography: 'caption' }}>{info}</Typography>

View File

@@ -20,7 +20,7 @@ import DialogClaimSubmitMember from './DialogClaimSubmitMember';
/* ---------------------------------- types --------------------------------- */
type CardBalanceProps = {
type CardPolicyProps = {
data: {
myLimit: {
balance: number;
@@ -60,7 +60,7 @@ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
/* -------------------------------------------------------------------------- */
export default function CardBalance(props: CardBalanceProps) {
export default function CardPolicy(props: CardPolicyProps) {
const [openDialog, setOpenDialog] = useState(false);
const [dialogTitle, setDialogTitle] = useState('');
const [isDialog, setIsDialog] = useState('');

View File

@@ -1,315 +0,0 @@
// @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 DashboardTable() {
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' }}>
Add Data
</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 />
<Stack>
{/* The Main Table */}
<TableContainer component={Paper}>
<Table aria-label="collapsible table">
<TableBody>
<TableRow>
<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}
/> */}
</Stack>
</Card>
);
}

View File

@@ -1,106 +0,0 @@
// @mui
import { styled } from '@mui/material/styles';
import { Button, Card, Divider, Link, Typography, Stack } from '@mui/material';
import { ChevronRight } from '@mui/icons-material';
// components
import Iconify from '../../components/Iconify';
// ----------------------------------------------------------------------
const RootStyle = styled(Card)(({ theme }) => ({
boxShadow: 'none',
padding: '1rem 0.5rem',
color: 'black',
backgroundColor: theme.palette.grey[200],
maxHeight: '240px',
}));
const ItemStyle = styled(Card)(({ theme }) => ({
boxShadow: 'none',
padding: theme.spacing(1),
borderRadius: 0.5,
color: 'black',
maxHeight: '170px',
}));
const itemList = [
{ info: 'Mohon lengkapi dokumen Alison Born', date: 'Selasa, 13 Februari 23', time: '09:43 WIB' },
];
// ----------------------------------------------------------------------
export default function NotificationCard() {
return (
<RootStyle>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography>
<Typography
variant="body2"
component="span"
sx={{ display: 'flex', alignItems: 'center' }}
>
<Iconify icon="eva:bell-fill" marginRight={0.75} />
Notification
<div
style={{
width: '12px',
height: '12px',
backgroundColor: '#19BBBB',
marginLeft: '0.5rem',
borderRadius: '50%',
}}
/>
</Typography>
</Typography>
<Button sx={{ typography: 'body2' }} endIcon={<ChevronRight />}>
View All
</Button>
</Stack>
<Stack sx={{ marginTop: 2 }}>
<ItemStyle>
{itemList.map(({ info, date, time }, key) => (
<div key={key}>
{key >= 1 ? <Divider sx={{ marginY: 0.5 }} /> : ''}
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="column" justifyContent="flex-start" alignItems="flex-start">
<Typography sx={{ typography: 'caption' }}>{info}</Typography>
<Link
component="button"
variant="caption"
underline="always"
onClick={() => {
alert('Info Detail');
}}
>
Info Detail
</Link>
</Stack>
<Stack direction="column" justifyContent="flex-start" alignItems="flex-start">
<Typography sx={{ typography: 'caption', color: '#656565' }}>{date}</Typography>
<Typography sx={{ typography: 'caption', color: '#656565' }}>{time}</Typography>
</Stack>
</Stack>
</div>
))}
</ItemStyle>
</Stack>
{/* <BorderLinearProgress variant="determinate" value={50} 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>
</Stack>
<Stack direction="row" spacing={2}>
<Button variant="outlined" fullWidth={true}>
Submit Claim
</Button>
<Button variant="contained" startIcon={<Payments />} fullWidth={true}>
Top Up
</Button>
</Stack> */}
</RootStyle>
);
}

View File

@@ -1,443 +0,0 @@
/* ---------------------------------- @mui ---------------------------------- */
import { styled } from '@mui/material/styles';
import {
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Button,
TableSortLabel,
Box,
IconButton,
Card,
Grid,
FormControl,
InputLabel,
Select,
MenuItem,
SelectChangeEvent,
Stack,
Typography,
LinearProgress,
linearProgressClasses,
} from '@mui/material';
import { visuallyHidden } from '@mui/utils';
/* ---------------------------------- axios --------------------------------- */
import axios from '../../utils/axios';
/* ---------------------------------- react --------------------------------- */
import { useContext, useEffect, useState } from 'react';
/* -------------------------------- component ------------------------------- */
import Iconify from '../../components/Iconify';
import BaseTablePagination from '../../components/BaseTablePagination';
/* ---------------------------------- theme --------------------------------- */
import palette from '../../theme/palette';
import { useSearchParams } from 'react-router-dom';
import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate';
import { fSplit } from '../../utils/formatNumber';
/* ---------------------------------- types --------------------------------- */
type PaginationTableProps = {
current_page: number;
from: number;
last_page: number;
links: [];
path: string;
per_page: number;
to: number;
total: number;
};
type DataTableProps = {
fullName: string;
memberId: string;
division: string;
limit: {
current: number;
total: number;
percentage: number;
};
status: number;
};
type Order = 'asc' | 'desc';
interface HeadCell {
id: string;
align: string;
label: string;
isSort: boolean;
}
interface EnhancedTableProps {
onRequestSort: (event: React.MouseEvent<unknown>, property: string) => void;
order: Order;
orderBy: string;
}
type DivisionDataProps = {
id: number;
name: string;
};
/* -------------------------------------------------------------------------- */
/* --------------------------------- styled --------------------------------- */
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 6,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: '#D1F1F1',
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 6,
backgroundColor: theme.palette.primary.main,
},
}));
/* -------------------------------------------------------------------------- */
/* -------------------------- enchanced table head -------------------------- */
const headCells: readonly HeadCell[] = [
{
id: 'member_id',
align: 'left',
label: 'Member ID',
isSort: true,
},
{
id: 'name',
align: 'center',
label: 'Name',
isSort: true,
},
{
id: 'division',
align: 'center',
label: 'Divisi',
isSort: false,
},
{
id: 'limit',
align: 'center',
label: 'Limit',
isSort: false,
},
{
id: 'active',
align: 'center',
label: 'Status',
isSort: true,
},
];
function EnhancedTableHead({ order, orderBy, onRequestSort }: EnhancedTableProps) {
const createSortHandler = (property: string) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
sortDirection={orderBy === headCell.id ? order : false}
// @ts-ignore
align={headCell.align}
sx={{ padding: 2 }}
>
{headCell.isSort ? (
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orderBy === headCell.id ? (
<Box component="span" sx={visuallyHidden}>
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</Box>
) : null}
</TableSortLabel>
) : (
headCell.label
)}
</TableCell>
))}
<TableCell align="center">{''}</TableCell>
</TableRow>
</TableHead>
);
}
/* -------------------------------------------------------------------------- */
export default function TableList(props: any) {
const { corporateValue } = useContext(UserCurrentCorporateContext);
const [dataTable, setDataTable] = useState([]);
const [paginationTable, setPaginationTable] = useState<PaginationTableProps>({
current_page: 0,
from: 0,
last_page: 0,
links: [],
path: '',
per_page: 0,
to: 0,
total: 0,
});
const [isLoading, setIsLoading] = useState(true);
const [searchParams, setSearchParams] = useSearchParams();
const [appliedParams, setAppliedParams] = useState({});
const [order, setOrder] = useState<Order>('asc');
const [orderBy, setOrderBy] = useState('name');
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
/* ------------------------------- handle sort ------------------------------ */
const handleRequestSort = async (event: React.MouseEvent<unknown>, property: string) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
const params = Object.fromEntries([
...searchParams.entries(),
['order', isAsc ? 'desc' : 'asc'],
['orderBy', property],
]);
setAppliedParams(params);
};
/* -------------------------------------------------------------------------- */
/* ----------------------------- 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,
newPage: number
) => {
setIsLoading(true);
const params = Object.fromEntries([...searchParams.entries(), ['page', newPage + 1]]);
setPage(newPage);
await new Promise((resolve) => setTimeout(resolve, 500));
setAppliedParams(params);
setIsLoading(false);
};
/* -------------------------------------------------------------------------- */
/* --------------------------- row page per limit --------------------------- */
const onRowsPerPageChangeHandle = async (event: React.ChangeEvent<HTMLInputElement>) => {
setIsLoading(true);
searchParams.delete('page');
const params = Object.fromEntries([
...searchParams.entries(),
['per_page', parseInt(event.target.value, 10)],
]);
setRowsPerPage(parseInt(event.target.value, 10));
await new Promise((resolve) => setTimeout(resolve, 500));
setAppliedParams(params);
setIsLoading(false);
};
/* -------------------------------------------------------------------------- */
useEffect(() => {
(async () => {
setIsLoading(true);
const division = await axios.get(`${corporateValue}/division`);
setDivisionData(division.data);
const params =
Object.keys(appliedParams).length !== 0
? appliedParams
: Object.fromEntries([...searchParams.entries(), ['order', order], ['orderBy', orderBy]]);
const corporateMembers = await axios.get(`${corporateValue}/members`, {
params: { ...params, claimMember: false },
});
setSearchParams(params);
setDataTable(corporateMembers.data.data);
setPaginationTable(corporateMembers.data);
setRowsPerPage(corporateMembers.data.per_page);
setIsLoading(false);
})();
}, [appliedParams, searchParams, order, orderBy, setSearchParams, corporateValue]);
return (
<Card>
<Grid container>
{/* Field 1 */}
<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>
</Grid>
</Grid>
{/* End Field 1 */}
{/* Field 2 */}
<Grid item xs={12}>
<TableContainer component={Paper}>
<Table aria-label="collapsible table" size="small">
<EnhancedTableHead
order={order}
orderBy={orderBy}
onRequestSort={handleRequestSort}
/>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={6} align="center">
Loading . . .
</TableCell>
</TableRow>
) : dataTable.length >= 1 ? (
dataTable.map((row: DataTableProps, index) => (
<TableRow key={index}>
<TableCell align="left">{row.memberId}</TableCell>
<TableCell align="center">{row.fullName}</TableCell>
<TableCell align="center">{row.division}</TableCell>
<TableCell align="center" width={170}>
<Stack>
<BorderLinearProgress
variant="determinate"
value={row.limit.percentage}
sx={{ mb: 1 }}
/>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
{fSplit(row.limit.current)} / {fSplit(row.limit.total)}
</Typography>
</Stack>
</TableCell>
<TableCell align="center">
{row.status === 1 ? (
<Button
sx={{
backgroundColor: 'rgba(84, 214, 44, 0.16)',
color: palette.dark.success.dark,
paddingY: 0,
'&:hover': {
backgroundColor: 'rgba(84, 214, 44, 0.32)',
color: palette.dark.success.darker,
},
}}
>
Active
</Button>
) : (
<Button
sx={{
backgroundColor: 'rgba(255, 72, 66, 0.16)',
color: palette.dark.error.dark,
paddingY: 0,
'&:hover': {
backgroundColor: 'rgba(255, 72, 66, 0.32)',
color: palette.dark.error.darker,
},
}}
>
Inactive
</Button>
)}
</TableCell>
{/* <TableCell align="right">
<IconButton>
<Iconify icon="ic:baseline-more-vert" />
</IconButton>
</TableCell> */}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} align="center">
No Data Found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination */}
<BaseTablePagination
count={paginationTable.total}
onPageChange={onPageChangeHandle}
page={page}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={onRowsPerPageChangeHandle}
/>
</Grid>
{/* End Field 2 */}
</Grid>
</Card>
);
}