From 3c5f06ef265b33e432645f36e5870691eda06a63 Mon Sep 17 00:00:00 2001 From: Fajar Date: Fri, 24 Mar 2023 09:14:49 +0700 Subject: [PATCH] create table component & connect api for dashboard --- .../Api/CorporateMemberController.php | 38 +-- .../Api/CorporatePolicyController.php | 10 +- .../LimitResources.php} | 23 +- .../{ => Dashboard}/MemberResources.php | 8 +- app/Services/CorporateMemberService.php | 49 +++ config/database.php | 4 +- frontend/client-portal/src/@types/policy.ts | 11 + frontend/client-portal/src/@types/table.ts | 66 ++++ .../client-portal/src/components/Table.tsx | 289 ++++++++++++++++ .../src/pages/Dashboard/Dashboard.tsx | 93 ------ .../src/pages/Dashboard/Index.tsx | 290 ++++++++++++++++ frontend/client-portal/src/routes/index.tsx | 2 +- .../src/sections/dashboard/BalanceCard.tsx | 103 ------ .../sections/dashboard/CardNotification.tsx | 15 +- .../{CardBalance.tsx => CardPolicy.tsx} | 4 +- .../src/sections/dashboard/DashboardTable.tsx | 315 ------------------ .../sections/dashboard/NotificationCard.tsx | 106 ------ 17 files changed, 745 insertions(+), 681 deletions(-) rename Modules/Client/Transformers/{DashboardResources.php => Dashboard/LimitResources.php} (55%) rename Modules/Client/Transformers/{ => Dashboard}/MemberResources.php (78%) create mode 100644 app/Services/CorporateMemberService.php create mode 100644 frontend/client-portal/src/@types/policy.ts create mode 100644 frontend/client-portal/src/@types/table.ts create mode 100644 frontend/client-portal/src/components/Table.tsx delete mode 100755 frontend/client-portal/src/pages/Dashboard/Dashboard.tsx create mode 100755 frontend/client-portal/src/pages/Dashboard/Index.tsx delete mode 100755 frontend/client-portal/src/sections/dashboard/BalanceCard.tsx rename frontend/client-portal/src/sections/dashboard/{CardBalance.tsx => CardPolicy.tsx} (98%) delete mode 100755 frontend/client-portal/src/sections/dashboard/DashboardTable.tsx delete mode 100755 frontend/client-portal/src/sections/dashboard/NotificationCard.tsx diff --git a/Modules/Client/Http/Controllers/Api/CorporateMemberController.php b/Modules/Client/Http/Controllers/Api/CorporateMemberController.php index 524efd58..d0367231 100755 --- a/Modules/Client/Http/Controllers/Api/CorporateMemberController.php +++ b/Modules/Client/Http/Controllers/Api/CorporateMemberController.php @@ -3,50 +3,26 @@ 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 = $this->corporateMemberService->getAllDashboardMembers($corporate_id, $request); - $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; - return response()->json(Helper::paginateResources(MemberResources::collection($members))); } diff --git a/Modules/Client/Http/Controllers/Api/CorporatePolicyController.php b/Modules/Client/Http/Controllers/Api/CorporatePolicyController.php index 45eba51d..3e72628d 100755 --- a/Modules/Client/Http/Controllers/Api/CorporatePolicyController.php +++ b/Modules/Client/Http/Controllers/Api/CorporatePolicyController.php @@ -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); } /** diff --git a/Modules/Client/Transformers/DashboardResources.php b/Modules/Client/Transformers/Dashboard/LimitResources.php similarity index 55% rename from Modules/Client/Transformers/DashboardResources.php rename to Modules/Client/Transformers/Dashboard/LimitResources.php index 717dc30c..871b1a40 100644 --- a/Modules/Client/Transformers/DashboardResources.php +++ b/Modules/Client/Transformers/Dashboard/LimitResources.php @@ -1,11 +1,10 @@ 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 + ] ]; } } diff --git a/Modules/Client/Transformers/MemberResources.php b/Modules/Client/Transformers/Dashboard/MemberResources.php similarity index 78% rename from Modules/Client/Transformers/MemberResources.php rename to Modules/Client/Transformers/Dashboard/MemberResources.php index fd91aa10..0d979f6b 100644 --- a/Modules/Client/Transformers/MemberResources.php +++ b/Modules/Client/Transformers/Dashboard/MemberResources.php @@ -1,6 +1,6 @@ $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, ]; } } diff --git a/app/Services/CorporateMemberService.php b/app/Services/CorporateMemberService.php new file mode 100644 index 00000000..2abdea30 --- /dev/null +++ b/app/Services/CorporateMemberService.php @@ -0,0 +1,49 @@ +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); + } +} diff --git a/config/database.php b/config/database.php index 6b3c0fc2..e529ac2f 100755 --- a/config/database.php +++ b/config/database.php @@ -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' => [ diff --git a/frontend/client-portal/src/@types/policy.ts b/frontend/client-portal/src/@types/policy.ts new file mode 100644 index 00000000..d38fa626 --- /dev/null +++ b/frontend/client-portal/src/@types/policy.ts @@ -0,0 +1,11 @@ +export type PolicyProps = { + myLimit: { + balance: number; + total: number; + percentage: number; + }; + lockLimit: { + balance: number; + percentage: number; + }; +}; diff --git a/frontend/client-portal/src/@types/table.ts b/frontend/client-portal/src/@types/table.ts new file mode 100644 index 00000000..e54f0cec --- /dev/null +++ b/frontend/client-portal/src/@types/table.ts @@ -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 = { + id: Extract; + align: string; + label: string; + isSort: boolean; + width?: number; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------------- row ---------------------------------- */ +export type TableListProps = { + headCells?: HeadCell[]; + rows?: Array; + paginations: { + page: number; + setPage: Dispatch>; + rowsPerPage: number; + setRowsPerPage: Dispatch>; + paginationTable: PaginationTableProps; + setPaginationTable: Dispatch>; + }; + orders: { + order: Order; + setOrder: Dispatch>; + orderBy: string; + setOrderBy: Dispatch>; + }; + loadings: { + isLoading: boolean; + setIsLoading: Dispatch>; + }; + params: { + searchParams: URLSearchParams; + setSearchParams: any; + appliedParams: {}; + setAppliedParams: Dispatch>; + }; +}; +/* -------------------------------------------------------------------------- */ diff --git a/frontend/client-portal/src/components/Table.tsx b/frontend/client-portal/src/components/Table.tsx new file mode 100644 index 00000000..9cb58a4e --- /dev/null +++ b/frontend/client-portal/src/components/Table.tsx @@ -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({ + headCells, + rows, + paginations, + orders, + loadings, + params, +}: TableListProps) { + /* ------------------------------- handle sort ------------------------------ */ + const handleRequestSort = async (event: React.MouseEvent, 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) => { + handleRequestSort(event, property); + }; + + return ( + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + ); + }; + /* -------------------------------------------------------------------------- */ + + /* ----------------------------- 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) => { + // 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 | 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) => { + 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 ( + + + {/* Field 1 */} + {/* + + + + Division + + + + +
+ setSearchText(event.target.value)} + value={searchText} + fullWidth + /> + +
+
+
*/} + {/* End Field 1 */} + {/* Field 2 */} + + {/* Table */} + + + {/* Table Header */} + + {/* End Table Header */} + {/* Table Body */} + + {loadings.isLoading ? ( + + + Loading . . . + + + ) : rows && rows.length >= 1 ? ( + rows.map((row, rowIndex) => ( + + {headCells && + //@ts-ignore + headCells.map((head, headIndex) => ( + //@ts-ignore + + {row[head.id]} + + ))} + + )) + ) : ( + + + No Data Found + + + )} + + {/* End Table Body */} + + + {/* End Table */} + + {/* Pagination */} + + {/* End Pagination */} + + {/* End Field 2 */} +
+
+ ); +} diff --git a/frontend/client-portal/src/pages/Dashboard/Dashboard.tsx b/frontend/client-portal/src/pages/Dashboard/Dashboard.tsx deleted file mode 100755 index 82128bc3..00000000 --- a/frontend/client-portal/src/pages/Dashboard/Dashboard.tsx +++ /dev/null @@ -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(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 ( - - - - - Dashboard - - - - - - - - - - - - - - - - - ); -} diff --git a/frontend/client-portal/src/pages/Dashboard/Index.tsx b/frontend/client-portal/src/pages/Dashboard/Index.tsx new file mode 100755 index 00000000..1946dc42 --- /dev/null +++ b/frontend/client-portal/src/pages/Dashboard/Index.tsx @@ -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(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('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({ + 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[] = [ + { + 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: ( + + + + {fSplit(obj.limit.current)} / {fSplit(obj.limit.total)} + + + ), + status: + obj.status === 1 ? ( + + ) : ( + + ), + action: ( + + + + ), + }; + }); + + return ( + + + + + Dashboard + + + + + + + + + + + + + {/* */} + + + + + ); +} diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index 704cdf95..5228100b 100755 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -140,7 +140,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 diff --git a/frontend/client-portal/src/sections/dashboard/BalanceCard.tsx b/frontend/client-portal/src/sections/dashboard/BalanceCard.tsx deleted file mode 100755 index b2155ad6..00000000 --- a/frontend/client-portal/src/sections/dashboard/BalanceCard.tsx +++ /dev/null @@ -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 ( - - -
- - Total Limit - - {fCurrency(TOTAL)} - / {INITIAL} -
- - - - {PERCENT}% - - -
- - - - - - - - Lock Fund ( 25% ) - - - - 125.000.000 / 125.000.000 - - - - - - - -
- ); -} diff --git a/frontend/client-portal/src/sections/dashboard/CardNotification.tsx b/frontend/client-portal/src/sections/dashboard/CardNotification.tsx index a0d07784..7727e61d 100644 --- a/frontend/client-portal/src/sections/dashboard/CardNotification.tsx +++ b/frontend/client-portal/src/sections/dashboard/CardNotification.tsx @@ -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) { - + {data - ? data.map(({ info, date, time }, key) => ( -
- {key >= 1 ? : ''} + ? data.map(({ info, date, time }, index) => ( +
+ {index >= 1 ? : ''} {info} diff --git a/frontend/client-portal/src/sections/dashboard/CardBalance.tsx b/frontend/client-portal/src/sections/dashboard/CardPolicy.tsx similarity index 98% rename from frontend/client-portal/src/sections/dashboard/CardBalance.tsx rename to frontend/client-portal/src/sections/dashboard/CardPolicy.tsx index 301e8a72..d7f8d4e1 100644 --- a/frontend/client-portal/src/sections/dashboard/CardBalance.tsx +++ b/frontend/client-portal/src/sections/dashboard/CardPolicy.tsx @@ -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(''); diff --git a/frontend/client-portal/src/sections/dashboard/DashboardTable.tsx b/frontend/client-portal/src/sections/dashboard/DashboardTable.tsx deleted file mode 100755 index 032d50dd..00000000 --- a/frontend/client-portal/src/sections/dashboard/DashboardTable.tsx +++ /dev/null @@ -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(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 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(options[0]); - const [inputValue, setInputValue] = React.useState(''); - - return ( -
- - {/* Filter Division */} - { - setValue(newValue); - }} - inputValue={inputValue} - onInputChange={(event, newInputValue) => { - setInputValue(newInputValue); - }} - id="controllable-states-demo" - options={options} - sx={{ minWidth: 240 }} - renderInput={(params) => } - /> - {/* Search */} - - - {/* Button Import */} - - {/* */} - {/* Button Add Task */} - - -
- ); - } - - // 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 [dataTableData, setDataTableData] = useState({ - current_page: 1, - data: [], - path: '', - first_page_url: '', - last_page: 1, - last_page_url: '', - next_page_url: '', - prev_page_url: '', - per_page: 10, - from: 0, - to: 0, - total: 0, - }); - - const loadDataTableData = async (appliedFilter: any | null = null) => { - setDataTableLoading(true); - const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); - const response = await axios.get('/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 ( - - - - {/* The Main Table */} - -
- - - - MemberID - - - Name - - - Divisi - - - Limit - - - Status - - - Action - - - - {dataTableIsLoading ? ( - - - - No Data Found - - - - ) : dataTableData.data.length === 0 ? ( - - - - No Data - - - - ) : ( - - {/* {dataTableData.data.map((row) => ( - - ))} */} - Testing - - )} -
- - - {/* */} - - - ); -} diff --git a/frontend/client-portal/src/sections/dashboard/NotificationCard.tsx b/frontend/client-portal/src/sections/dashboard/NotificationCard.tsx deleted file mode 100755 index 4ff1100e..00000000 --- a/frontend/client-portal/src/sections/dashboard/NotificationCard.tsx +++ /dev/null @@ -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 ( - - - - - - Notification -
- - - - - - - - {itemList.map(({ info, date, time }, key) => ( -
- {key >= 1 ? : ''} - - - {info} - { - alert('Info Detail'); - }} - > - Info Detail - - - - {date} - {time} - - -
- ))} -
-
- - {/* - - - Lock Fund ( 25% ) - 125.000.000 / 125.000.000 - - - - - - */} - - ); -}