[WIP] Improvement
This commit is contained in:
46
Modules/Client/Http/Controllers/Api/AuthController.php
Normal file
46
Modules/Client/Http/Controllers/Api/AuthController.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Client\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
public function login(Request $request)
|
||||||
|
{
|
||||||
|
$request->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.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
88
Modules/Client/Http/Controllers/Api/DashboardController.php
Normal file
88
Modules/Client/Http/Controllers/Api/DashboardController.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Client\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
* @return Renderable
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$user = auth()->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)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Modules/Client/Http/Controllers/Api/MemberController.php
Normal file
101
Modules/Client/Http/Controllers/Api/MemberController.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Client\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Helpers\Helper;
|
||||||
|
use App\Models\Member;
|
||||||
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
|
||||||
|
class MemberController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
* @return Renderable
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = auth()->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)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Modules/Client/Http/Controllers/Api/UserController.php
Normal file
79
Modules/Client/Http/Controllers/Api/UserController.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Client\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
* @return Renderable
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return response()->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)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Modules\Client\Http\Controllers\Api\AuthController;
|
||||||
|
use Modules\Client\Http\Controllers\Api\DashboardController;
|
||||||
|
use Modules\Client\Http\Controllers\Api\MemberController;
|
||||||
|
use Modules\Client\Http\Controllers\Api\UserController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -13,6 +17,20 @@ use Illuminate\Http\Request;
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::middleware('auth:api')->get('/client', function (Request $request) {
|
Route::prefix('client')->group(function () {
|
||||||
return $request->user();
|
|
||||||
});
|
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']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|||||||
@@ -63,6 +63,18 @@ class Corporate extends Model
|
|||||||
return $this->hasMany(CorporateDivision::class, 'corporate_id');
|
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()
|
public function importLogs()
|
||||||
{
|
{
|
||||||
return $this->morphMany(ImportLog::class, 'importable');
|
return $this->morphMany(ImportLog::class, 'importable');
|
||||||
|
|||||||
@@ -41,4 +41,9 @@ class User extends Authenticatable
|
|||||||
protected $casts = [
|
protected $casts = [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function managedCorporates()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Corporate::class, 'corporate_manager', 'user_id', 'corporate_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('corporate_manager', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
};
|
||||||
11
frontend/client-portal/src/@types/diagnosis.ts
Normal file
11
frontend/client-portal/src/@types/diagnosis.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
21
frontend/client-portal/src/@types/member.ts
Normal file
21
frontend/client-portal/src/@types/member.ts
Normal file
@@ -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,
|
||||||
|
};
|
||||||
14
frontend/client-portal/src/@types/paginated-data.ts
Normal file
14
frontend/client-portal/src/@types/paginated-data.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
17
frontend/client-portal/src/components/BasePagination.tsx
Normal file
17
frontend/client-portal/src/components/BasePagination.tsx
Normal file
@@ -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 (
|
||||||
|
<Box sx={{ m: 2 }} display="flex" justifyContent="flex-end">
|
||||||
|
<Pagination count={paginationData?.last_page} page={paginationData?.current_page} variant="outlined" shape="rounded" onChange={onPageChange}/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
92
frontend/client-portal/src/components/Breadcrumbs.tsx
Normal file
92
frontend/client-portal/src/components/Breadcrumbs.tsx
Normal file
@@ -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) => <LinkItem key={link.name} link={link} />);
|
||||||
|
|
||||||
|
const listActiveLast = links.map((link) => (
|
||||||
|
<div key={link.name}>
|
||||||
|
{link.name !== currentLink ? (
|
||||||
|
<LinkItem link={link} />
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
maxWidth: 260,
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
color: 'text.disabled',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentLink}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MUIBreadcrumbs
|
||||||
|
separator={
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ width: 4, height: 4, borderRadius: '50%', bgcolor: 'text.disabled' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{activeLast ? listDefault : listActiveLast}
|
||||||
|
</MUIBreadcrumbs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type LinkItemProps = {
|
||||||
|
link: TLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LinkItem({ link }: LinkItemProps) {
|
||||||
|
const { href, name, icon } = link;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={name}
|
||||||
|
variant="body2"
|
||||||
|
component={RouterLink}
|
||||||
|
to={href || '#'}
|
||||||
|
sx={{
|
||||||
|
lineHeight: 2,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: 'text.primary',
|
||||||
|
'& > div': { display: 'inherit' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon && <Box sx={{ mr: 1, '& svg': { width: 20, height: 20 } }}>{icon}</Box>}
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
60
frontend/client-portal/src/components/HeaderBreadcrumbs.tsx
Normal file
60
frontend/client-portal/src/components/HeaderBreadcrumbs.tsx
Normal file
@@ -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 (
|
||||||
|
<Box sx={{ mb: 5, ...sx }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
{heading}
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs links={links} {...other} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{action && <Box sx={{ flexShrink: 0 }}>{action}</Box>}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
{isString(moreLink) ? (
|
||||||
|
<Link href={moreLink} target="_blank" rel="noopener" variant="body2">
|
||||||
|
{moreLink}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
moreLink.map((href) => (
|
||||||
|
<Link
|
||||||
|
noWrap
|
||||||
|
key={href}
|
||||||
|
href={href}
|
||||||
|
variant="body2"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
sx={{ display: 'table' }}
|
||||||
|
>
|
||||||
|
{href}
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -150,11 +150,13 @@ export default function EditorToolbar({ id, isSimple, ...other }: EditorToolbarP
|
|||||||
<select className="ql-align" />
|
<select className="ql-align" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ql-formats">
|
{!isSimple && (
|
||||||
<button type="button" className="ql-link" />
|
<div className="ql-formats">
|
||||||
<button type="button" className="ql-image" />
|
<button type="button" className="ql-link" />
|
||||||
<button type="button" className="ql-video" />
|
<button type="button" className="ql-image" />
|
||||||
</div>
|
<button type="button" className="ql-video" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="ql-formats">
|
<div className="ql-formats">
|
||||||
{!isSimple && <button type="button" className="ql-formula" />}
|
{!isSimple && <button type="button" className="ql-formula" />}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import ReactQuill, { ReactQuillProps } from 'react-quill';
|
import ReactQuill from 'react-quill';
|
||||||
|
import 'react-quill/dist/quill.snow.css';
|
||||||
// @mui
|
// @mui
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { Box, BoxProps } from '@mui/material';
|
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;
|
id?: string;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
simple?: boolean;
|
simple?: boolean;
|
||||||
helperText?: ReactNode;
|
helperText?: ReactNode;
|
||||||
sx?: BoxProps;
|
sx?: BoxProps;
|
||||||
@@ -64,7 +67,7 @@ export default function Editor({
|
|||||||
maxStack: 100,
|
maxStack: 100,
|
||||||
userOnly: true,
|
userOnly: true,
|
||||||
},
|
},
|
||||||
syntax: true,
|
syntax: false, // need highlightjs
|
||||||
clipboard: {
|
clipboard: {
|
||||||
matchVisual: false,
|
matchVisual: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<Controller
|
||||||
|
name={name}
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
const onSelected = (option: optionsCustomInterface) =>
|
||||||
|
field.value.includes(option.value)
|
||||||
|
? field.value.filter((value: string) => value !== option.value)
|
||||||
|
: [...field.value, option.value];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup>
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={index}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value.includes(option.value)}
|
||||||
|
onChange={() => field.onChange(onSelected(option))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={option.label}
|
||||||
|
{...other}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<Controller
|
||||||
|
name={name}
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||||
|
<MobileDatePicker
|
||||||
|
inputFormat="yyyy-MM-dd"
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
renderInput={(field) => <TextField {...field} fullWidth error={!!error} helperText={error?.message} {...other} />}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ export default function RHFEditor({ name, ...other }: Props) {
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
|
simple={true}
|
||||||
helperText={
|
helperText={
|
||||||
<FormHelperText error sx={{ px: 2, textTransform: 'capitalize' }}>
|
<FormHelperText error sx={{ px: 2, textTransform: 'capitalize' }}>
|
||||||
{error?.message}
|
{error?.message}
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ export { default as RHFSelect } from './RHFSelect';
|
|||||||
export { default as RHFEditor } from './RHFEditor';
|
export { default as RHFEditor } from './RHFEditor';
|
||||||
export { default as RHFTextField } from './RHFTextField';
|
export { default as RHFTextField } from './RHFTextField';
|
||||||
export { default as RHFRadioGroup } from './RHFRadioGroup';
|
export { default as RHFRadioGroup } from './RHFRadioGroup';
|
||||||
|
export { default as RHFDatepicker } from './RHFDatepicker';
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ function NavListSub({ list }: NavListSubProps) {
|
|||||||
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const active = getActive(list.path, pathname);
|
const active = getActive(list.path, pathname, list.openWhen);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export function isExternalLink(path: string) {
|
|||||||
return path.includes('http');
|
return path.includes('http');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getActive(path: string, pathname: string) {
|
export function getActive(path: string, pathname: string, openWhen?: string[]) {
|
||||||
return path ? !!matchPath({ path: path, end: false }, pathname) : false;
|
const listPathWhenActive = [ ...openWhen ?? [], path ];
|
||||||
|
|
||||||
|
return listPathWhenActive.includes(pathname);
|
||||||
|
// return path ? !!matchPath({ path: path, end: false }, pathname) : false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type NavListProps = {
|
|||||||
path: string;
|
path: string;
|
||||||
icon?: ReactElement;
|
icon?: ReactElement;
|
||||||
info?: ReactElement;
|
info?: ReactElement;
|
||||||
|
openWhen? : string[];
|
||||||
children?: {
|
children?: {
|
||||||
title: string;
|
title: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type NavListRootProps = {
|
|||||||
export function NavListRoot({ list, isCollapse }: NavListRootProps) {
|
export function NavListRoot({ list, isCollapse }: NavListRootProps) {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const active = getActive(list.path, pathname);
|
const active = getActive(list.path, pathname, list.openWhen);
|
||||||
|
|
||||||
const [open, setOpen] = useState(active);
|
const [open, setOpen] = useState(active);
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ export default function NavSectionVertical({
|
|||||||
}: NavSectionProps) {
|
}: NavSectionProps) {
|
||||||
return (
|
return (
|
||||||
<Box {...other}>
|
<Box {...other}>
|
||||||
{navConfig.map((group) => (
|
{navConfig.map((group, index) => (
|
||||||
<List key={group.subheader} disablePadding sx={{ px: 2 }}>
|
<List key={index} disablePadding sx={{ px: 2 }}>
|
||||||
<ListSubheaderStyle
|
<ListSubheaderStyle
|
||||||
|
key={index}
|
||||||
sx={{
|
sx={{
|
||||||
...(isCollapse && {
|
...(isCollapse && {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import axios from '../utils/axios';
|
|||||||
import useAuth from '../hooks/useAuth';
|
import useAuth from '../hooks/useAuth';
|
||||||
import SomethingUsage from '../sections/dashboard/SomethingUsage';
|
import SomethingUsage from '../sections/dashboard/SomethingUsage';
|
||||||
import { fCurrency } from '../utils/formatNumber';
|
import { fCurrency } from '../utils/formatNumber';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -15,11 +16,23 @@ export default function Dashboard() {
|
|||||||
const { themeStretch } = useSettings();
|
const { themeStretch } = useSettings();
|
||||||
|
|
||||||
const { logout } = useAuth();
|
const { logout } = useAuth();
|
||||||
|
const [ corporate, setCorporate ] = useState({});
|
||||||
|
|
||||||
const loadSomething = () => {
|
const loadSomething = () => {
|
||||||
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 }) => ({
|
const DangerCard = styled(Card)(({ theme }) => ({
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
|
|||||||
86
frontend/client-portal/src/pages/Members/Create.tsx
Normal file
86
frontend/client-portal/src/pages/Members/Create.tsx
Normal file
@@ -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 (
|
||||||
|
<Page title={pageTitle}>
|
||||||
|
|
||||||
|
<HeaderBreadcrumbs
|
||||||
|
heading={pageTitle}
|
||||||
|
links={[
|
||||||
|
{
|
||||||
|
name: 'Master',
|
||||||
|
href: '/master',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Formularium',
|
||||||
|
href: '/master/formularium/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
href: '/master/formularium/create/',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Card sx={{ p: 2 }}>
|
||||||
|
<Form isSubmitting={isSubmitting} isEdit={isEdit} currentFormularium={currentFormularium} />
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
239
frontend/client-portal/src/pages/Members/Form.tsx
Normal file
239
frontend/client-portal/src/pages/Members/Form.tsx
Normal file
@@ -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<Corporate> {
|
||||||
|
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<FormValuesProps>({
|
||||||
|
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<HTMLInputElement>(null)
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const [currentImportFileName, setCurrentImportFileName] = useState<string|null>(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 (
|
||||||
|
|
||||||
|
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
|
||||||
|
<Typography variant="h6">Formularium Detail</Typography>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<RHFTextField name="code" label="Code" />
|
||||||
|
{(!(currentFormularium?.id) && <Typography variant="caption">Will be generated if empty</Typography>)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RHFTextField name="name" label="Name" />
|
||||||
|
|
||||||
|
<Typography variant="h6">Formularium Drug List Import</Typography>
|
||||||
|
|
||||||
|
<input type='file' id='file' ref={importForm} style={{ display: 'none' }} onChange={handleImportChange} accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain" />
|
||||||
|
<ButtonGroup variant="outlined" aria-label="outlined button group" fullWidth>
|
||||||
|
<Button onClick={handleImportButton} fullWidth>{currentImportFileName ?? "No File Selected"}</Button>
|
||||||
|
{(currentImportFileName && <Button onClick={handleCancelImportButton} size="small" fullWidth={false} sx={{ p: 1.8 }}><CancelIcon color="error"/></Button>)}
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<LoadingButton type="submit" variant="contained" size="large" fullWidth={true} loading={isSubmitting}>
|
||||||
|
{!isEdit ? 'Save New Corporate' : 'Save Update'}
|
||||||
|
</LoadingButton>
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,319 +1,38 @@
|
|||||||
// @mui
|
import { Card, Grid } from "@mui/material";
|
||||||
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 { useParams } from "react-router-dom";
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs";
|
||||||
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
import Page from "../../components/Page";
|
||||||
import PublishIcon from '@mui/icons-material/Publish';
|
import useSettings from "../../hooks/useSettings";
|
||||||
// hooks
|
import List from "./List";
|
||||||
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';
|
|
||||||
|
|
||||||
export default function Members() {
|
|
||||||
|
|
||||||
|
export default function Drugs() {
|
||||||
const { themeStretch } = useSettings();
|
const { themeStretch } = useSettings();
|
||||||
|
|
||||||
const { logout } = useAuth();
|
const { corporate_id } = useParams();
|
||||||
|
|
||||||
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<typeof createData> }) {
|
|
||||||
const { row } = props;
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
|
||||||
<TableCell>
|
|
||||||
<IconButton
|
|
||||||
aria-label="expand row"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setOpen(!open)}
|
|
||||||
>
|
|
||||||
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="left">{row.code}</TableCell>
|
|
||||||
<TableCell align="left">{row.name}</TableCell>
|
|
||||||
<TableCell align="right">{row.nik}</TableCell>
|
|
||||||
<TableCell align="right">{row.plan_code}</TableCell>
|
|
||||||
<TableCell align="right">{row.number_of_claim}</TableCell>
|
|
||||||
<TableCell align="right">{row.number_of_families}</TableCell>
|
|
||||||
<TableCell align="right"><Button variant="outlined" color="success" size="small">Active</Button></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
|
||||||
<Box sx={{ margin: 1 }}>
|
|
||||||
<Typography variant="h6" gutterBottom component="div">
|
|
||||||
History
|
|
||||||
</Typography>
|
|
||||||
<Table size="small" aria-label="purchases">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Date</TableCell>
|
|
||||||
<TableCell>Customer</TableCell>
|
|
||||||
<TableCell align="right">Amount</TableCell>
|
|
||||||
<TableCell align="right">Total price ($)</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{row.history ? row.history.map((historyRow) => (
|
|
||||||
<TableRow key={historyRow?.date}>
|
|
||||||
<TableCell component="th" scope="row">
|
|
||||||
{historyRow?.date}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{historyRow?.customerId}</TableCell>
|
|
||||||
<TableCell align="right">{historyRow?.amount}</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
{Math.round(historyRow?.amount * 1000 * 100) / 100}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
: (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={8}>No Data</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</Collapse>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dummy Default Data
|
|
||||||
const [memberLoading, setMemberLoading] = React.useState(true);
|
|
||||||
const [members, setMembers] = React.useState<Member[]>([]);
|
|
||||||
|
|
||||||
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<string[]>([]);
|
|
||||||
|
|
||||||
const handleChangePlanID = (event: SelectChangeEvent<typeof planIdFilter>) => {
|
|
||||||
const {
|
|
||||||
target: { value },
|
|
||||||
} = event;
|
|
||||||
setPlanIdFilter(
|
|
||||||
// On autofill we get a stringified value.
|
|
||||||
typeof value === 'string' ? value.split(',') : value,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<string[]>([]);
|
|
||||||
const handleChangeStatus = (event: SelectChangeEvent<typeof statusFilter>) => {
|
|
||||||
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 pageTitle = 'Formularium';
|
||||||
return (
|
return (
|
||||||
<Page title="Member List">
|
<Page title={ pageTitle }>
|
||||||
<Container maxWidth={themeStretch ? false : 'xl'}>
|
|
||||||
<Typography variant="h3" component="h1" paragraph>
|
|
||||||
Member List
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Grid container spacing={2} sx={{ mb: 2 }} justifyContent="flex-end">
|
<HeaderBreadcrumbs
|
||||||
<Grid item>
|
heading={ pageTitle }
|
||||||
<TextField id="outlined-basic" label="Search" variant="outlined" sx={{ width: 400 }}/>
|
links={[
|
||||||
</Grid>
|
{
|
||||||
|
name: 'Master',
|
||||||
<Grid item>
|
href: '/master',
|
||||||
<FormControl sx={{ width: 200 }}>
|
},
|
||||||
<InputLabel id="plan-id-label">PlanID</InputLabel>
|
{
|
||||||
<Select
|
name: 'Formularium',
|
||||||
labelId="plan-id-label"
|
href: '/master/formulariums',
|
||||||
id="plan-id"
|
},
|
||||||
multiple
|
]}
|
||||||
value={planIdFilter}
|
/>
|
||||||
onChange={handleChangePlanID}
|
|
||||||
input={<OutlinedInput label="PlanID" />}
|
<Card>
|
||||||
MenuProps={MenuProps}
|
<List />
|
||||||
>
|
</Card>
|
||||||
{names.map((name) => (
|
|
||||||
<MenuItem
|
|
||||||
key={name}
|
|
||||||
value={name}
|
|
||||||
style={getStyles(name, planIdFilter, theme)}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item>
|
|
||||||
<FormControl sx={{ width: 200 }}>
|
|
||||||
<InputLabel id="status-filter-label">Status</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="status-filter-label"
|
|
||||||
id="status-filter"
|
|
||||||
multiple
|
|
||||||
value={statusFilter}
|
|
||||||
onChange={handleChangeStatus}
|
|
||||||
input={<OutlinedInput label="Status" />}
|
|
||||||
MenuProps={MenuProps}
|
|
||||||
>
|
|
||||||
{['Active', 'Suspended'].map((name) => (
|
|
||||||
<MenuItem
|
|
||||||
key={name}
|
|
||||||
value={name}
|
|
||||||
style={getStyles(name, statusFilter, theme)}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item>
|
|
||||||
<input id="importMember" ref={importMember} style={{ display: 'none' }} type="file" accept='.csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, text/plain' />
|
|
||||||
<Button variant="outlined" startIcon={<PublishIcon />} sx={{ p: 1.8 }} onClick={handleImportButton}>
|
|
||||||
Import
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table aria-label="collapsible table">
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell style={headStyle} align="left">Detail</TableCell>
|
|
||||||
<TableCell style={headStyle} align="left">MemberID</TableCell>
|
|
||||||
<TableCell style={headStyle} align="left">Name</TableCell>
|
|
||||||
<TableCell style={headStyle} align="right">NIK</TableCell>
|
|
||||||
<TableCell style={headStyle} align="right">PlanID</TableCell>
|
|
||||||
<TableCell style={headStyle} align="right">Claim (time)</TableCell>
|
|
||||||
<TableCell style={headStyle} align="right">Family (person)</TableCell>
|
|
||||||
<TableCell style={headStyle} align="right">Status</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
{memberLoading ?
|
|
||||||
(
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={8} align="center">Loading</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
) : (
|
|
||||||
members.length == 0 ?
|
|
||||||
(
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={8} align="center">No Data</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
) : (
|
|
||||||
<TableBody>
|
|
||||||
{members.map(row => (
|
|
||||||
<Row key={row.code} row={row} />
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Card>
|
|
||||||
</Container>
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
314
frontend/client-portal/src/pages/Members/List.tsx
Normal file
314
frontend/client-portal/src/pages/Members/List.tsx
Normal file
@@ -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<HTMLInputElement>(null);
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
|
|
||||||
|
const handleSearchChange = (event: any) => {
|
||||||
|
const newSearchText = event.target.value ?? ''
|
||||||
|
setSearchText(newSearchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearchSubmit = (event: any) => {
|
||||||
|
event.preventDefault();
|
||||||
|
props.onSearch(searchText); // Trigger to Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => { // Trigger First Search
|
||||||
|
setSearchText(searchParams.get('search') ?? '');
|
||||||
|
}, [searchParams])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSearchSubmit} style={{ width: '100%' }}>
|
||||||
|
<TextField id="search-input" ref={searchInput} label="Search" variant="outlined" fullWidth onChange={handleSearchChange} value={searchText}/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImportForm(props: any) {
|
||||||
|
// IMPORT
|
||||||
|
// Create Button Menu
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const createMenu = Boolean(anchorEl);
|
||||||
|
const importForm = useRef<HTMLInputElement>(null)
|
||||||
|
const [currentImportFileName, setCurrentImportFileName] = useState(null)
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<input type='file' id='file' ref={importForm} style={{ display: 'none' }} onChange={handleImportChange} accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain" />
|
||||||
|
{( !currentImportFileName && <Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
|
||||||
|
<SearchInput onSearch={applyFilter}/>
|
||||||
|
{/* <h1>kjasndkjandskjasndkjansdkjansd</h1> */}
|
||||||
|
<Button
|
||||||
|
id="import-button"
|
||||||
|
variant='outlined'
|
||||||
|
startIcon={<AddIcon />} sx={{ p: 1.8 }}
|
||||||
|
aria-controls={createMenu ? 'basic-menu' : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={createMenu ? 'true' : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
id="import-button"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={createMenu}
|
||||||
|
onClose={handleClose}
|
||||||
|
MenuListProps={{
|
||||||
|
'aria-labelledby': 'basic-button',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={() => {navigate('/master/formularium/create')} }>Create</MenuItem>
|
||||||
|
<MenuItem onClick={handleImportButton}>Import</MenuItem>
|
||||||
|
<MenuItem onClick={handleClose}>Download Template</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{( currentImportFileName && <Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
|
||||||
|
<ButtonGroup variant="outlined" aria-label="outlined button group" fullWidth>
|
||||||
|
<Button onClick={handleImportButton} fullWidth>{currentImportFileName ?? "No File Selected"}</Button>
|
||||||
|
<Button onClick={handleCancelImportButton} size="small" fullWidth={false} sx={{ p: 1.8 }}><CancelIcon color="error"/></Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
id="upload-button"
|
||||||
|
variant='outlined'
|
||||||
|
startIcon={<UploadIcon />} sx={{ p: 1.8 }}
|
||||||
|
onClick={handleUpload}
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{( importResult &&
|
||||||
|
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
|
||||||
|
<Box sx={{ color: "text.secondary" }}>Last Import Result Report : <a href={importResult.result_file?.url ?? "#"}>{importResult.result_file?.name ?? "-"}</a></Box>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called on every row to map the data to the columns
|
||||||
|
function createData( member: Member ): Member {
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the every row of the table
|
||||||
|
function Row(props: { row: ReturnType<typeof createData> }) {
|
||||||
|
const { row } = props;
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton
|
||||||
|
aria-label="expand row"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
>
|
||||||
|
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="left">{row.member_id}</TableCell>
|
||||||
|
<TableCell align="left">{row.payor_id}</TableCell>
|
||||||
|
<TableCell align="left">{row.name}</TableCell>
|
||||||
|
<TableCell align="left">{row.nik}</TableCell>
|
||||||
|
<TableCell align="left">{row.nric}</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="right"><Button variant="outlined" color="success" size="small">Active</Button></TableCell>
|
||||||
|
{/* <TableCell align="right"><Button variant="outlined" color="error" size="small">Disable</Button></TableCell> */}
|
||||||
|
</TableRow>
|
||||||
|
{/* COLLAPSIBLE ROW */}
|
||||||
|
<TableRow>
|
||||||
|
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
|
||||||
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
|
<Box sx={{ borderBottom: 1 }}>
|
||||||
|
<Typography variant="body2" gutterBottom component="div">
|
||||||
|
Description : {row.description}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dummy Default Data
|
||||||
|
const [dataTableIsLoading, setDataTableLoading] = useState(true);
|
||||||
|
const [dataTableLastRequest, setDataTableLastRequest] = useState(0);
|
||||||
|
const [dataTableResponseState, setDataTableResponseState] = useState('idle');
|
||||||
|
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>({
|
||||||
|
current_page: 1,
|
||||||
|
data: [],
|
||||||
|
path: "",
|
||||||
|
first_page_url: "",
|
||||||
|
last_page: 1,
|
||||||
|
last_page_url: "",
|
||||||
|
next_page_url: "",
|
||||||
|
prev_page_url: "",
|
||||||
|
per_page: 10,
|
||||||
|
from: 0,
|
||||||
|
to: 0,
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
const [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 (
|
||||||
|
<Stack>
|
||||||
|
<ImportForm />
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
{/* The Main Table */}
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table aria-label="collapsible table">
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell style={headStyle} align="left">Detail</TableCell>
|
||||||
|
<TableCell style={headStyle} align="left">MemberID</TableCell>
|
||||||
|
<TableCell style={headStyle} align="left">PayorID</TableCell>
|
||||||
|
<TableCell style={headStyle} align="left">Name</TableCell>
|
||||||
|
<TableCell style={headStyle} align="left">NIK</TableCell>
|
||||||
|
<TableCell style={headStyle} align="left">PlanID</TableCell>
|
||||||
|
<TableCell style={headStyle} align="right" width={100}>Status</TableCell>
|
||||||
|
{/* <TableCell style={headStyle} align="right" width={100}>Action</TableCell> */}
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
{dataTableIsLoading ?
|
||||||
|
(
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={8} align="center">Loading</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
) : (
|
||||||
|
dataTableData.data.length == 0 ?
|
||||||
|
(
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={8} align="center">No Data</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
) : (
|
||||||
|
<TableBody>
|
||||||
|
{dataTableData.data.map(row => (
|
||||||
|
<Row key={row.id} row={row} />
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<BasePagination paginationData={dataTableData} onPageChange={handlePageChange}/>
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user