diff --git a/Modules/Client/Http/Controllers/Api/AuthController.php b/Modules/Client/Http/Controllers/Api/AuthController.php new file mode 100644 index 00000000..8e36cde6 --- /dev/null +++ b/Modules/Client/Http/Controllers/Api/AuthController.php @@ -0,0 +1,46 @@ +validate([ + 'email' => 'required|email', + 'password' => 'required' + ]); + + $user = User::query() + ->where('email', $request->email) + ->first(); + + if (!$user) { + return response(['message' => 'User Tidak Ditemukan'], 404); + } + + if (!Hash::check($request->password, $user->password)) { + return response(['message' => 'Password Salah'], 403); + } + + return response([ + 'message' => 'Selamat Datang', + 'user' => $user, + 'token' => $user->createToken('app')->plainTextToken + ]); + } + + public function logout(Request $request) + { + $token = $request->bearerToken(); + Auth::user()->tokens()->where('id', $token)->delete(); + + return response(['message' => 'Berhasil Logout.']); + } +} diff --git a/Modules/Client/Http/Controllers/Api/DashboardController.php b/Modules/Client/Http/Controllers/Api/DashboardController.php new file mode 100644 index 00000000..5067b1cd --- /dev/null +++ b/Modules/Client/Http/Controllers/Api/DashboardController.php @@ -0,0 +1,88 @@ +user(); + + $corporate = $user->managedCorporates() + ->withCount('employees') + ->with(['policies' => function ($policy) { + $policy->limit(1)->latest(); + }]) + ->first(); + + return response()->json(compact('corporate')); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('client::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('client::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('client::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } +} diff --git a/Modules/Client/Http/Controllers/Api/MemberController.php b/Modules/Client/Http/Controllers/Api/MemberController.php new file mode 100644 index 00000000..9897450d --- /dev/null +++ b/Modules/Client/Http/Controllers/Api/MemberController.php @@ -0,0 +1,101 @@ +user(); + + $corporate = $user->managedCorporates()->first(); + // $plans = + + $members = Member::query() + ->whereHas('employeds', function($corporateEmployee) use ($corporate) { + $corporateEmployee->where('corporate_id', $corporate->id); + }); + if ($request->has('search')) { + $members + ->where('member_id', 'like', "%" . $request->search . "%") + ->orWhere('payor_id', 'like', "%" . $request->search . "%") + ->orWhere('name', 'like', "%" . $request->search . "%"); + } + + $members = $members->paginate(); + + return response()->json([ + 'members' => Helper::paginateResources($members) + ]); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('client::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('client::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('client::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } +} diff --git a/Modules/Client/Http/Controllers/Api/UserController.php b/Modules/Client/Http/Controllers/Api/UserController.php new file mode 100644 index 00000000..b7cb8812 --- /dev/null +++ b/Modules/Client/Http/Controllers/Api/UserController.php @@ -0,0 +1,79 @@ +json(auth()->user()); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('client::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('client::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('client::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } +} diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index c5e89beb..09b10e41 100644 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -1,6 +1,10 @@ get('/client', function (Request $request) { - return $request->user(); -}); \ No newline at end of file +Route::prefix('client')->group(function () { + + Route::post('login', [AuthController::class, 'login'])->name('login'); + Route::post('forget-password', [AuthController::class, 'forgetPassword'])->name('forget-password'); + Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email'); + + Route::middleware('auth:sanctum')->group(function () { + + Route::post('logout', [AuthController::class, 'logout'])->name('logout'); + Route::get('/user', [UserController::class, 'index']); + + Route::get('dashboard', [DashboardController::class, 'index']); + Route::get('members', [MemberController::class, 'index']); + + }); + +}); diff --git a/app/Models/Corporate.php b/app/Models/Corporate.php index 51b35a1f..7014f21e 100644 --- a/app/Models/Corporate.php +++ b/app/Models/Corporate.php @@ -63,6 +63,18 @@ class Corporate extends Model return $this->hasMany(CorporateDivision::class, 'corporate_id'); } + public function employees() + { + return $this->belongsToMany(Member::class, 'corporate_employees', 'corporate_id', 'member_id')->withPivot([ + 'branch_code', + 'division_id', + 'nik', + 'status', + 'start', + 'end' + ]); + } + public function importLogs() { return $this->morphMany(ImportLog::class, 'importable'); diff --git a/app/Models/User.php b/app/Models/User.php index 89963686..281e04a5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -41,4 +41,9 @@ class User extends Authenticatable protected $casts = [ 'email_verified_at' => 'datetime', ]; + + public function managedCorporates() + { + return $this->belongsToMany(Corporate::class, 'corporate_manager', 'user_id', 'corporate_id'); + } } diff --git a/database/migrations/2022_08_26_064247_create_corporate_manager_table.php b/database/migrations/2022_08_26_064247_create_corporate_manager_table.php new file mode 100644 index 00000000..11bd31b6 --- /dev/null +++ b/database/migrations/2022_08_26_064247_create_corporate_manager_table.php @@ -0,0 +1,41 @@ +id(); + $table->foreignId('corporate_id'); + $table->foreignId('user_id'); + + $table->timestamps(); + $table->softDeletes(); + + $table->foreignId('created_by')->nullable(); + $table->foreignId('updated_by')->nullable(); + $table->foreignId('deleted_by')->nullable(); + + $table->index(['corporate_id', 'user_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('corporate_manager'); + } +}; diff --git a/frontend/client-portal/src/@types/diagnosis.ts b/frontend/client-portal/src/@types/diagnosis.ts new file mode 100644 index 00000000..a33734b5 --- /dev/null +++ b/frontend/client-portal/src/@types/diagnosis.ts @@ -0,0 +1,11 @@ +export type Icd = { + id: number; + type: string; + rev: string; + version?: string; + code: string; + name: string; + description?: any; + childs?: Icd[]; + status: string; +}; diff --git a/frontend/client-portal/src/@types/member.ts b/frontend/client-portal/src/@types/member.ts new file mode 100644 index 00000000..c38b0640 --- /dev/null +++ b/frontend/client-portal/src/@types/member.ts @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------- + +export type Member = { + id: string, + member_id: string, + record_type: string, + payor_id: string, + user_id: string, + name_prefix: string, + name: string, + name_suffix: string, + birth_date: string, + gender: string, + language: string, + race: string, + marital_status: string, + principal_id: string, + relation_with_principal: string, + bpjs_class: string, + active: string, +}; diff --git a/frontend/client-portal/src/@types/paginated-data.ts b/frontend/client-portal/src/@types/paginated-data.ts new file mode 100644 index 00000000..738f8785 --- /dev/null +++ b/frontend/client-portal/src/@types/paginated-data.ts @@ -0,0 +1,14 @@ +export type LaravelPaginatedData = { + current_page: number; + data: any[]; + path: string; + first_page_url: string; + last_page: number; + last_page_url: string; + next_page_url?: string; + prev_page_url?: string; + per_page: number; + from?: number; + to?: number; + total: number; +} diff --git a/frontend/client-portal/src/components/BasePagination.tsx b/frontend/client-portal/src/components/BasePagination.tsx new file mode 100644 index 00000000..f099aa1a --- /dev/null +++ b/frontend/client-portal/src/components/BasePagination.tsx @@ -0,0 +1,17 @@ +import { Pagination } from "@mui/material"; +import { Box } from "@mui/system"; +import { LaravelPaginatedData } from "../@types/paginated-data"; + + +export interface Props { + paginationData?: LaravelPaginatedData; + onPageChange: any; +} + +export default function BasePagination({ paginationData, onPageChange }: Props) { + return ( + + + + ) +} diff --git a/frontend/client-portal/src/components/Breadcrumbs.tsx b/frontend/client-portal/src/components/Breadcrumbs.tsx new file mode 100644 index 00000000..834e965c --- /dev/null +++ b/frontend/client-portal/src/components/Breadcrumbs.tsx @@ -0,0 +1,92 @@ +import { ReactElement } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +// @mui +import { + Box, + Link, + Typography, + BreadcrumbsProps, + Breadcrumbs as MUIBreadcrumbs, +} from '@mui/material'; + +// ---------------------------------------------------------------------- + +type TLink = { + href?: string; + name: string; + icon?: ReactElement; +}; + +export interface Props extends BreadcrumbsProps { + links: TLink[]; + activeLast?: boolean; +} + +export default function Breadcrumbs({ links, activeLast = false, ...other }: Props) { + const currentLink = links[links.length - 1].name; + + const listDefault = links.map((link) => ); + + const listActiveLast = links.map((link) => ( +
+ {link.name !== currentLink ? ( + + ) : ( + + {currentLink} + + )} +
+ )); + + return ( + + } + {...other} + > + {activeLast ? listDefault : listActiveLast} + + ); +} + +// ---------------------------------------------------------------------- + +type LinkItemProps = { + link: TLink; +}; + +function LinkItem({ link }: LinkItemProps) { + const { href, name, icon } = link; + return ( + div': { display: 'inherit' }, + }} + > + {icon && {icon}} + {name} + + ); +} diff --git a/frontend/client-portal/src/components/HeaderBreadcrumbs.tsx b/frontend/client-portal/src/components/HeaderBreadcrumbs.tsx new file mode 100644 index 00000000..ddf66cc4 --- /dev/null +++ b/frontend/client-portal/src/components/HeaderBreadcrumbs.tsx @@ -0,0 +1,60 @@ +import { ReactNode } from 'react'; +import { isString } from 'lodash'; +// @mui +import { Box, Typography, Link } from '@mui/material'; +// +import Breadcrumbs, { Props as BreadcrumbsProps } from './Breadcrumbs'; + +// ---------------------------------------------------------------------- + +interface Props extends BreadcrumbsProps { + action?: ReactNode; + heading: string; + moreLink?: string | string[]; +} + +export default function HeaderBreadcrumbs({ + links, + action, + heading, + moreLink = '' || [], + sx, + ...other +}: Props) { + return ( + + + + + {heading} + + + + + {action && {action}} + + + + {isString(moreLink) ? ( + + {moreLink} + + ) : ( + moreLink.map((href) => ( + + {href} + + )) + )} + + + ); +} diff --git a/frontend/client-portal/src/components/editor/EditorToolbar.tsx b/frontend/client-portal/src/components/editor/EditorToolbar.tsx index d750dac2..4d22dd1f 100644 --- a/frontend/client-portal/src/components/editor/EditorToolbar.tsx +++ b/frontend/client-portal/src/components/editor/EditorToolbar.tsx @@ -150,11 +150,13 @@ export default function EditorToolbar({ id, isSimple, ...other }: EditorToolbarP + + + {(currentImportFileName && )} + + + + {!isEdit ? 'Save New Corporate' : 'Save Update'} + + + + + ); +}; diff --git a/frontend/client-portal/src/pages/Members/Index.tsx b/frontend/client-portal/src/pages/Members/Index.tsx index ed5ef6a4..4629baaf 100644 --- a/frontend/client-portal/src/pages/Members/Index.tsx +++ b/frontend/client-portal/src/pages/Members/Index.tsx @@ -1,319 +1,38 @@ -// @mui -import { Box, Button, Card, Collapse, Container, FormControl, Grid, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge } from '@mui/material'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; -import PublishIcon from '@mui/icons-material/Publish'; -// hooks -import useSettings from '../../hooks/useSettings'; -// components -import Page from '../../components/Page'; -import axios from '../../utils/axios'; -import useAuth from '../../hooks/useAuth'; -import { Link } from 'react-router-dom'; -import React, { useEffect, useRef } from 'react'; -import { Theme, useTheme } from '@mui/material/styles'; +import { Card, Grid } from "@mui/material"; +import { useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs"; +import Page from "../../components/Page"; +import useSettings from "../../hooks/useSettings"; +import List from "./List"; -export default function Members() { + + +export default function Drugs() { const { themeStretch } = useSettings(); - const { logout } = useAuth(); - - const loadSomething = () => { - console.log('Loading Something') - } - - type Member = { - id: number; - code: string; - nik: string; - name: string; - plan_code: string; - number_of_families: number; - number_of_claim: number; - active: boolean; - history: any[]; - } - - function createData( member: Member ): Member { - return { - ...member, - history: [ - { - date: '2020-01-05', - customerId: '11091700', - amount: 3, - }, - { - date: '2020-01-02', - customerId: 'Anonymous', - amount: 1, - }, - ] - } - } - - function Row(props: { row: ReturnType }) { - const { row } = props; - const [open, setOpen] = React.useState(false); - - return ( - - *': { borderBottom: 'unset' } }}> - - setOpen(!open)} - > - {open ? : } - - - {row.code} - {row.name} - {row.nik} - {row.plan_code} - {row.number_of_claim} - {row.number_of_families} - - - - - - - - History - - - - - Date - Customer - Amount - Total price ($) - - - - {row.history ? row.history.map((historyRow) => ( - - - {historyRow?.date} - - {historyRow?.customerId} - {historyRow?.amount} - - {Math.round(historyRow?.amount * 1000 * 100) / 100} - - - )) - : ( - - No Data - - ) - } - -
-
-
-
-
-
- ); - } - - // Dummy Default Data - const [memberLoading, setMemberLoading] = React.useState(true); - const [members, setMembers] = React.useState([]); - - const loadMembers = async () => { - setMemberLoading(true) - const response = await axios.get('/members'); - setMemberLoading(false) - setMembers(response.data.map(createData)); - } - - useEffect(() => { - loadMembers(); - }, []) - - const headStyle = { - fontWeight: 'bold', - }; - - // FILTER SELECT - const ITEM_HEIGHT = 48; - const ITEM_PADDING_TOP = 8; - const MenuProps = { - PaperProps: { - style: { - maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, - width: 250, - }, - }, - }; - - const names = [ - 'PLAN001', - 'PLAN002', - 'PLAN003', - 'PLAN004', - 'PLAN005', - ]; - function getStyles(name: string, personName: string[], theme: Theme) { - return { - fontWeight: - personName.indexOf(name) === -1 - ? theme.typography.fontWeightRegular - : theme.typography.fontWeightMedium, - }; - } - - const theme = useTheme(); - const [planIdFilter, setPlanIdFilter] = React.useState([]); - - const handleChangePlanID = (event: SelectChangeEvent) => { - const { - target: { value }, - } = event; - setPlanIdFilter( - // On autofill we get a stringified value. - typeof value === 'string' ? value.split(',') : value, - ); - }; - - const [statusFilter, setStatusFilter] = React.useState([]); - const handleChangeStatus = (event: SelectChangeEvent) => { - const { - target: { value }, - } = event; - setStatusFilter( - // On autofill we get a stringified value. - typeof value === 'string' ? value.split(',') : value, - ); - }; - // END FILTER SELECT - - // IMPORT - const importMember = React.useRef(null); - const handleImportButton = (event: any) => { - if (importMember?.current) - importMember.current.click() - else - alert('No file selected') - } + const { corporate_id } = useParams(); + const pageTitle = 'Formularium'; return ( - - - - Member List - + - - - - - - - - PlanID - - - - - - - Status - - - - - - - - - - - - - - - - Detail - MemberID - Name - NIK - PlanID - Claim (time) - Family (person) - Status - - - {memberLoading ? - ( - - - Loading - - - ) : ( - members.length == 0 ? - ( - - - No Data - - - ) : ( - - {members.map(row => ( - - ))} - - ) - )} -
-
-
-
+ + + + +
); } diff --git a/frontend/client-portal/src/pages/Members/List.tsx b/frontend/client-portal/src/pages/Members/List.tsx new file mode 100644 index 00000000..07bd9c79 --- /dev/null +++ b/frontend/client-portal/src/pages/Members/List.tsx @@ -0,0 +1,314 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../hooks/useSettings'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../utils/axios'; +import { LaravelPaginatedData } from '../../@types/paginated-data'; +import { Icd } from '../../@types/diagnosis'; +import BasePagination from '../../components/BasePagination'; +import { Member } from '../../@types/member'; + +export default function List() { + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const { corporate_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); // Trigger to Parent + } + + useEffect(() => { // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null) + const [currentImportFileName, setCurrentImportFileName] = useState(null) + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleImportButton = () => { + if (importForm?.current) { + handleClose(); + importForm.current ? importForm.current.click() : console.log('No File selected'); + } else { + alert('No file selected') + } + } + + const handleCancelImportButton = () => { + importForm.current.value = ""; + importForm.current.dispatchEvent(new Event("change", { bubbles: true })); + } + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name) + } else { + setCurrentImportFileName(null); + } + } + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append("file", importForm.current?.files[0]) + axios.post(`master/formularium/import`, formData ) + .then(response => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data) + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + }) + .catch(response => { + alert('Looks like something went wrong. Please check your data and try again. ' + response.message) + }) + } else { + alert('No File Selected') + } + } + + return ( +
+ + {( !currentImportFileName && + + {/*

kjasndkjandskjasndkjansdkjansd

*/} + + + {navigate('/master/formularium/create')} }>Create + Import + Download Template + +
+ )} + + {( currentImportFileName && + + + + + + + + )} + {( importResult && + + Last Import Result Report : {importResult.result_file?.name ?? "-"} + + )} +
+ ); + } + + // Called on every row to map the data to the columns + function createData( member: Member ): Member { + return { + ...member, + } + } + + // Generate the every row of the table + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)} + > + {open ? : } + + + {row.member_id} + {row.payor_id} + {row.name} + {row.nik} + {row.nric} + + + {/* */} + + {/* COLLAPSIBLE ROW */} + + + + + + Description : {row.description} + + + + + + + ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableLastRequest, setDataTableLastRequest] = useState(0); + const [dataTableResponseState, setDataTableResponseState] = useState('idle'); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 + }); + const [dataTablePage, setDataTablePage] = useState(5); + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/members', { params: filter }); + + setDataTableData(response.data.members); + setDataTableLoading(false); + } + + const headStyle = { + fontWeight: 'bold', + }; + + const applyFilter = async (searchFilter: string) => { + await loadDataTableData({ "search" : searchFilter }); + setSearchParams({ "search" : searchFilter }); + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ["page", value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + useEffect(() => { + loadDataTableData(); + }, []) + + return ( + + + + + {/* The Main Table */} + + + + + Detail + MemberID + PayorID + Name + NIK + PlanID + Status + {/* Action */} + + + {dataTableIsLoading ? + ( + + + Loading + + + ) : ( + dataTableData.data.length == 0 ? + ( + + + No Data + + + ) : ( + + {dataTableData.data.map(row => ( + + ))} + + ) + )} +
+
+ + +
+
+ ); +}