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
-
-
-
-
-
+ {!isSimple && (
+
+
+
+
+
+ )}
{!isSimple &&
}
diff --git a/frontend/client-portal/src/components/editor/index.tsx b/frontend/client-portal/src/components/editor/index.tsx
index b2629c92..5fb3ed88 100644
--- a/frontend/client-portal/src/components/editor/index.tsx
+++ b/frontend/client-portal/src/components/editor/index.tsx
@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
-import ReactQuill, { ReactQuillProps } from 'react-quill';
+import ReactQuill from 'react-quill';
+import 'react-quill/dist/quill.snow.css';
// @mui
import { styled } from '@mui/material/styles';
import { Box, BoxProps } from '@mui/material';
@@ -33,9 +34,11 @@ const RootStyle = styled(Box)(({ theme }) => ({
// ----------------------------------------------------------------------
-export interface Props extends ReactQuillProps {
+export interface Props {
id?: string;
error?: boolean;
+ value?: string;
+ onChange?: (value: string) => void;
simple?: boolean;
helperText?: ReactNode;
sx?: BoxProps;
@@ -64,7 +67,7 @@ export default function Editor({
maxStack: 100,
userOnly: true,
},
- syntax: true,
+ syntax: false, // need highlightjs
clipboard: {
matchVisual: false,
},
diff --git a/frontend/client-portal/src/components/hook-form/RHFCheckbox.tsx b/frontend/client-portal/src/components/hook-form/RHFCheckbox.tsx
index 66cf8af9..cac7f8bb 100644
--- a/frontend/client-portal/src/components/hook-form/RHFCheckbox.tsx
+++ b/frontend/client-portal/src/components/hook-form/RHFCheckbox.tsx
@@ -67,3 +67,49 @@ export function RHFMultiCheckbox({ name, options, ...other }: RHFMultiCheckboxPr
/>
);
}
+
+
+// ----------------------------------------------------------------------
+interface optionsCustomInterface {
+ value: string;
+ label: string;
+}
+interface RHFCustomMultiCheckboxProps {
+ name: string;
+ options: optionsCustomInterface[];
+}
+
+export function RHFCustomMultiCheckbox({ name, options, ...other }: RHFCustomMultiCheckboxProps) {
+ const { control } = useFormContext();
+
+ return (
+
{
+ const onSelected = (option: optionsCustomInterface) =>
+ field.value.includes(option.value)
+ ? field.value.filter((value: string) => value !== option.value)
+ : [...field.value, option.value];
+
+ return (
+
+ {options.map((option, index) => (
+ field.onChange(onSelected(option))}
+ />
+ }
+ label={option.label}
+ {...other}
+ />
+ ))}
+
+ );
+ }}
+ />
+ );
+}
diff --git a/frontend/client-portal/src/components/hook-form/RHFDatepicker.tsx b/frontend/client-portal/src/components/hook-form/RHFDatepicker.tsx
new file mode 100644
index 00000000..fb52d01f
--- /dev/null
+++ b/frontend/client-portal/src/components/hook-form/RHFDatepicker.tsx
@@ -0,0 +1,33 @@
+// form
+import { useFormContext, Controller } from 'react-hook-form';
+// @mui
+import { TextField, TextFieldProps } from '@mui/material';
+import { LocalizationProvider, MobileDatePicker } from '@mui/x-date-pickers';
+import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
+
+// ----------------------------------------------------------------------
+
+interface IProps {
+ name: string;
+}
+
+export default function RHFDatepicker({ name, ...other }: IProps & TextFieldProps) {
+ const { control } = useFormContext();
+ return (
+ (
+
+
+ }
+ />
+
+ )}
+ />
+ );
+}
diff --git a/frontend/client-portal/src/components/hook-form/RHFEditor.tsx b/frontend/client-portal/src/components/hook-form/RHFEditor.tsx
index 33140c2d..d492ec1c 100644
--- a/frontend/client-portal/src/components/hook-form/RHFEditor.tsx
+++ b/frontend/client-portal/src/components/hook-form/RHFEditor.tsx
@@ -24,6 +24,7 @@ export default function RHFEditor({ name, ...other }: Props) {
value={field.value}
onChange={field.onChange}
error={!!error}
+ simple={true}
helperText={
{error?.message}
diff --git a/frontend/client-portal/src/components/hook-form/index.ts b/frontend/client-portal/src/components/hook-form/index.ts
index 89b1727a..6d936b02 100644
--- a/frontend/client-portal/src/components/hook-form/index.ts
+++ b/frontend/client-portal/src/components/hook-form/index.ts
@@ -8,3 +8,4 @@ export { default as RHFSelect } from './RHFSelect';
export { default as RHFEditor } from './RHFEditor';
export { default as RHFTextField } from './RHFTextField';
export { default as RHFRadioGroup } from './RHFRadioGroup';
+export { default as RHFDatepicker } from './RHFDatepicker';
diff --git a/frontend/client-portal/src/components/nav-section/horizontal/NavList.tsx b/frontend/client-portal/src/components/nav-section/horizontal/NavList.tsx
index f04140c1..1bdc4741 100644
--- a/frontend/client-portal/src/components/nav-section/horizontal/NavList.tsx
+++ b/frontend/client-portal/src/components/nav-section/horizontal/NavList.tsx
@@ -83,7 +83,7 @@ function NavListSub({ list }: NavListSubProps) {
const { pathname } = useLocation();
- const active = getActive(list.path, pathname);
+ const active = getActive(list.path, pathname, list.openWhen);
const [open, setOpen] = useState(false);
diff --git a/frontend/client-portal/src/components/nav-section/index.ts b/frontend/client-portal/src/components/nav-section/index.ts
index 6202a182..c58b8d2c 100644
--- a/frontend/client-portal/src/components/nav-section/index.ts
+++ b/frontend/client-portal/src/components/nav-section/index.ts
@@ -9,6 +9,9 @@ export function isExternalLink(path: string) {
return path.includes('http');
}
-export function getActive(path: string, pathname: string) {
- return path ? !!matchPath({ path: path, end: false }, pathname) : false;
+export function getActive(path: string, pathname: string, openWhen?: string[]) {
+ const listPathWhenActive = [ ...openWhen ?? [], path ];
+
+ return listPathWhenActive.includes(pathname);
+ // return path ? !!matchPath({ path: path, end: false }, pathname) : false;
}
diff --git a/frontend/client-portal/src/components/nav-section/type.ts b/frontend/client-portal/src/components/nav-section/type.ts
index b1baf8e7..870407d8 100644
--- a/frontend/client-portal/src/components/nav-section/type.ts
+++ b/frontend/client-portal/src/components/nav-section/type.ts
@@ -8,6 +8,7 @@ export type NavListProps = {
path: string;
icon?: ReactElement;
info?: ReactElement;
+ openWhen? : string[];
children?: {
title: string;
path: string;
diff --git a/frontend/client-portal/src/components/nav-section/vertical/NavList.tsx b/frontend/client-portal/src/components/nav-section/vertical/NavList.tsx
index 8f781b5e..b5bb74b3 100644
--- a/frontend/client-portal/src/components/nav-section/vertical/NavList.tsx
+++ b/frontend/client-portal/src/components/nav-section/vertical/NavList.tsx
@@ -18,7 +18,7 @@ type NavListRootProps = {
export function NavListRoot({ list, isCollapse }: NavListRootProps) {
const { pathname } = useLocation();
- const active = getActive(list.path, pathname);
+ const active = getActive(list.path, pathname, list.openWhen);
const [open, setOpen] = useState(active);
diff --git a/frontend/client-portal/src/components/nav-section/vertical/index.tsx b/frontend/client-portal/src/components/nav-section/vertical/index.tsx
index 60942bd0..de247933 100644
--- a/frontend/client-portal/src/components/nav-section/vertical/index.tsx
+++ b/frontend/client-portal/src/components/nav-section/vertical/index.tsx
@@ -30,9 +30,10 @@ export default function NavSectionVertical({
}: NavSectionProps) {
return (
- {navConfig.map((group) => (
-
+ {navConfig.map((group, index) => (
+
{
- axios.get('/user')
+ // axios.get('/user')
+ axios.get('dashboard')
+ .then((res) => {
+ setCorporate(res.data.corporate)
+ })
+ .catch((err) => {
+ alert('Opps, Something Went Wrong when collecting dashboard data')
+ })
};
+ useEffect(() => {
+ loadSomething()
+ }, [])
+
const DangerCard = styled(Card)(({ theme }) => ({
boxShadow: 'none',
padding: theme.spacing(3),
diff --git a/frontend/client-portal/src/pages/Members/Create.tsx b/frontend/client-portal/src/pages/Members/Create.tsx
new file mode 100644
index 00000000..e0527248
--- /dev/null
+++ b/frontend/client-portal/src/pages/Members/Create.tsx
@@ -0,0 +1,86 @@
+import * as Yup from 'yup';
+import { yupResolver } from "@hookform/resolvers/yup";
+import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material";
+import { useForm } from "react-hook-form";
+import { useParams } from "react-router-dom";
+import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs";
+import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from "../../../components/hook-form";
+import Page from "../../../components/Page";
+import useSettings from "../../../hooks/useSettings";
+import { useMemo, useState } from 'react';
+import Form from "./Form";
+
+
+export default function Divisions() {
+ const { themeStretch } = useSettings();
+
+ const [isEdit, setIsEdit] = useState(false);
+
+ const [currentFormularium, setCurrentFormularium] = useState({});
+
+ const NewDivisionSchema = Yup.object().shape({
+ name: Yup.string().required('Name is required'),
+ code: Yup.string().required('Corporate Code is required'),
+ active: Yup.boolean().required('Corporate Status is required'),
+ });
+
+ const defaultValues = useMemo(
+ () => ({
+ code: '',
+ }),
+ []
+ );
+
+ const methods = useForm({
+ resolver: yupResolver(NewDivisionSchema),
+ defaultValues,
+ });
+
+ const {
+ reset,
+ watch,
+ control,
+ setValue,
+ getValues,
+ setError,
+ handleSubmit,
+ formState: { isSubmitting },
+ } = methods;
+
+ const onSubmit = async (data: any) => {
+ console.log(data)
+ };
+
+ const pageTitle = 'Create Formularium';
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/client-portal/src/pages/Members/Form.tsx b/frontend/client-portal/src/pages/Members/Form.tsx
new file mode 100644
index 00000000..9ec18471
--- /dev/null
+++ b/frontend/client-portal/src/pages/Members/Form.tsx
@@ -0,0 +1,239 @@
+import * as Yup from 'yup';
+import { useSnackbar } from 'notistack';
+import { useNavigate } from 'react-router-dom';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+// form
+import { useForm } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers/yup';
+// @mui
+import { styled } from '@mui/material/styles';
+import { LoadingButton } from '@mui/lab';
+import {
+ Box,
+ Button,
+ ButtonGroup,
+ Card,
+ FormHelperText,
+ Grid,
+ Stack,
+ Typography,
+} from '@mui/material';
+
+import CancelIcon from '@mui/icons-material/Cancel';
+
+// components
+import {
+ FormProvider,
+ RHFTextField,
+ RHFRadioGroup,
+ RHFUploadAvatar,
+ RHFSwitch,
+ RHFEditor,
+ RHFDatepicker,
+ RHFMultiCheckbox,
+ RHFCheckbox,
+ RHFCustomMultiCheckbox,
+} from '../../../components/hook-form';
+import { Corporate } from '../../../@types/corporates';
+import axios from '../../../utils/axios';
+import { fCurrency } from '../../../utils/formatNumber';
+
+const LabelStyle = styled(Typography)(({ theme }) => ({
+ ...theme.typography.subtitle2,
+ color: theme.palette.text.secondary,
+ marginBottom: theme.spacing(1),
+}));
+
+interface FormValuesProps extends Partial {
+ taxes: boolean;
+ inStock: boolean;
+}
+
+type Props = {
+ isEdit: boolean;
+ currentFormularium?: Corporate;
+};
+
+export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
+ const navigate = useNavigate();
+
+ // const [ errors, setErrors ] = useState<{ [key: string]: string }>({});
+
+ const { enqueueSnackbar } = useSnackbar();
+
+ const NewCorporateSchema = Yup.object().shape({
+ name: Yup.string().required('Name is required'),
+ // code: Yup.string().required('Corporate Code is required'),
+ // file: Yup.boolean().required('Corporate Status is required'),
+ });
+
+ const defaultValues = useMemo(
+ () => ({
+ code: currentFormularium?.code || '',
+ name: currentFormularium?.name || '',
+ }),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [currentFormularium]
+ );
+
+ const methods = useForm({
+ resolver: yupResolver(NewCorporateSchema),
+ defaultValues,
+ });
+
+ const {
+ reset,
+ watch,
+ control,
+ setValue,
+ getValues,
+ setError,
+ handleSubmit,
+ formState: { isSubmitting },
+ } = methods;
+
+ const values = watch();
+
+ useEffect(() => {
+ if (isEdit && currentFormularium) {
+ reset(defaultValues);
+ }
+ if (!isEdit) {
+ reset(defaultValues);
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isEdit, currentFormularium]);
+
+ const onSubmit = async (data: FormValuesProps) => {
+ try {
+ if (!isEdit) {
+ const response = await axios.post('/master/formulariums', data);
+ } else {
+ const response = await axios.put('/master/formulariums/' + currentFormularium?.id ?? '', data);
+ }
+ reset();
+ enqueueSnackbar(!isEdit ? 'Formularium Created Successfully!' : 'Formularium Udpated Successfully!', { variant: 'success' });
+ navigate('/master/formularium');
+ } catch (error: any) {
+ if (error && error.response.status === 422) {
+ for (const [key, value] of Object.entries(error.response.data.errors)) {
+ setError(key, { message: value[0] });
+ enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' });
+ }
+ }
+ else {
+ enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' });
+ }
+ }
+
+ const ascent = document?.querySelector("ascent");
+ if (ascent != null) {
+ ascent.innerHTML = "";
+ }
+ };
+
+ const handleDrop = useCallback(
+ (acceptedFiles) => {
+ setValue(
+ 'logo',
+ acceptedFiles.map((file: Blob | MediaSource) =>
+ Object.assign(file, {
+ preview: URL.createObjectURL(file),
+ })
+ )
+ );
+ },
+ [setValue]
+ );
+
+ const handleRemove = (file: File | string) => {
+ setValue('logo', null);
+ };
+
+ const linking_rules_checkbox_name = "linking_rules"
+ const linking_tools = [
+ {
+ "value" : "nrik",
+ "label" : "No. KTP"
+ },
+ {
+ "value" : "nik",
+ "label" : "Nomor Induk Karyawan (NIK)"
+ },
+ {
+ "value" : "member_id",
+ "label" : "Member ID"
+ },
+ {
+ "value" : "phone",
+ "label" : "Nomor Telepon"
+ },
+ {
+ "value" : "email",
+ "label" : "E-Mail"
+ },
+ ]
+
+
+ const importForm = useRef(null)
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [currentImportFileName, setCurrentImportFileName] = useState(null);
+
+ 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);
+ }
+ }
+
+
+ return (
+
+
+
+
+ Formularium Detail
+
+
+
+ {(!(currentFormularium?.id) && Will be generated if empty)}
+
+
+
+
+ Formularium Drug List Import
+
+
+
+
+ {(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
- }
- MenuProps={MenuProps}
- >
- {names.map((name) => (
-
- ))}
-
-
-
-
-
-
- Status
- }
- MenuProps={MenuProps}
- >
- {['Active', 'Suspended'].map((name) => (
-
- ))}
-
-
-
-
-
-
- } sx={{ p: 1.8 }} onClick={handleImportButton}>
- Import
-
-
-
-
-
-
-
-
-
- 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
*/}
+ } sx={{ p: 1.8 }}
+ aria-controls={createMenu ? 'basic-menu' : undefined}
+ aria-haspopup="true"
+ aria-expanded={createMenu ? 'true' : undefined}
+ onClick={handleClick}
+ >
+ Create
+
+
+
+ )}
+
+ {( currentImportFileName &&
+
+
+
+
+
+ } sx={{ p: 1.8 }}
+ onClick={handleUpload}
+ >
+ Upload
+
+
+ )}
+ {( 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 => (
+
+ ))}
+
+ )
+ )}
+
+
+
+
+
+
+ );
+}