Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ac97537cd | |||
| fc214b4bad | |||
| 450ae649c3 | |||
| 2eb12129bb | |||
| a26b9f5d11 | |||
| 35dcc853b8 | |||
| 368ca22e62 | |||
| c1c24db83d |
@@ -2,6 +2,7 @@ const QUERIES = {
|
||||
USERS_LIST: 'users-list',
|
||||
STARTED_LIST: 'started-list',
|
||||
READY_LIST: 'ready-list',
|
||||
VERIFIED_LIST: 'verified-list',
|
||||
PENDING_LIST: 'pending-list',
|
||||
APPROVED_LIST: 'approved-list',
|
||||
REJECTED_LIST: 'rejected-list',
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { UsersListHeader } from "../../../../app/modules/process/user-ready/components/header/UsersListHeader";
|
||||
|
||||
|
||||
type PaginatedListProps = {
|
||||
data: any,
|
||||
itemsPerPage?: number,
|
||||
filterItem?: string[],
|
||||
tableTitle?: string,
|
||||
titleClass?:string,
|
||||
children: (data:any) => ReactNode;
|
||||
}
|
||||
|
||||
export default function TestList({
|
||||
data,
|
||||
itemsPerPage = 5,
|
||||
filterItem,
|
||||
tableTitle,
|
||||
titleClass,
|
||||
children,
|
||||
}:PaginatedListProps) {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filteredData, setFilteredData] = useState(data);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [newData, setNewData] = useState<any>([]);
|
||||
|
||||
const numberOfSelection = itemsPerPage;
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentPage != 0) {
|
||||
setCurrentPage((prev) => prev - numberOfSelection);
|
||||
}
|
||||
};
|
||||
const handleNext = () => {
|
||||
if (currentPage < data.length) {
|
||||
setCurrentPage((prev) => prev + numberOfSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = ({ target: { value } }:{target: {value:string}}, name:string) => {
|
||||
setSearchTerm(value);
|
||||
let newFilteredData:any = data.filter((item:any) =>
|
||||
item[name].toLowerCase().startsWith(value.toLowerCase())
|
||||
);
|
||||
setFilteredData(newFilteredData);
|
||||
setCurrentPage(0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setNewData(
|
||||
filteredData?.slice(currentPage, numberOfSelection + currentPage)
|
||||
);
|
||||
}, [currentPage, filteredData]);
|
||||
|
||||
useEffect(()=>{
|
||||
setCurrentPage(0)
|
||||
},[itemsPerPage])
|
||||
|
||||
return (
|
||||
<div className="w-full d-flex flex-column h-100">
|
||||
<h1 className={`text-2xl mb-5 font-semibold ${titleClass && titleClass}`}>{tableTitle}</h1>
|
||||
|
||||
{data.length > 0 && filterItem && (
|
||||
// <div className="mb-10 flex justify-end items-center gap-2">
|
||||
// {filterItem.map((item, index) => (
|
||||
// <label
|
||||
// key={index}
|
||||
// className="flex flex-col sm:flex-row items-center gap-2 text-slate-600 dark:text-slate-100 transition-all duration-500"
|
||||
// >
|
||||
// Search by {item[0].toUpperCase() + item.slice(1)}
|
||||
// <input
|
||||
// name={item}
|
||||
// type="text"
|
||||
// className="py-1 px-2 text-sm min-w-[100px] text-black dark:text-white bg-white dark:bg-slate-800 rounded-full border-0 outline-none ring-1 ring-slate-300 dark:ring-white transition-all duration-500"
|
||||
// value={searchTerm}
|
||||
// onChange={(e) => {
|
||||
// handleSearch(e, item);
|
||||
// }}
|
||||
// />
|
||||
// </label>
|
||||
// ))}
|
||||
// </div>
|
||||
<UsersListHeader />
|
||||
)}
|
||||
|
||||
{children(newData)}
|
||||
|
||||
{/* show prev and next button if data exist */}
|
||||
{(data.length > 0 && data.length > itemsPerPage) && (
|
||||
<div className="w-full h-100 d-flex gap-4 justify-content-center align-items-end">
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
className={`text-sm md:text-lg d-flex justify-content-center align-items-center border-1 transition-all duration-300 ${
|
||||
currentPage == 0
|
||||
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pe-none"
|
||||
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
|
||||
}`}
|
||||
disabled={currentPage == 0}
|
||||
// style={{width:'30px', height:'30px'}}
|
||||
>
|
||||
< Previous
|
||||
</button>
|
||||
|
||||
{/* {data.length && data.map((item, index)=>{
|
||||
item = item
|
||||
if(index%itemsPerPage == 0 && index >= currentPage && index <= currentPage+itemsPerPage){
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={handleNext}
|
||||
className={`text-sm md:text-lg rounded-circle d-flex justify-content-center align-items-center border-1 transition-all duration-300 ${
|
||||
currentPage != index
|
||||
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400"
|
||||
: "text-slate-600 border-slate-600 dark:text-white dark:border-white pe-none"
|
||||
}`}
|
||||
disabled={currentPage != index}
|
||||
style={{width:'30px', height:'30px'}}
|
||||
>
|
||||
{index/itemsPerPage +1}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
})} */}
|
||||
|
||||
<button
|
||||
onClick={handleNext}
|
||||
className={`text-sm md:text-lg d-flex justify-content-center align-items-center border-1 transition-all duration-300 ${
|
||||
currentPage + numberOfSelection >= data.length
|
||||
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pe-none"
|
||||
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
|
||||
}`}
|
||||
disabled={currentPage + numberOfSelection >= data.length}
|
||||
// style={{width:'30px', height:'30px'}}
|
||||
>
|
||||
Next >
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ type Props = {
|
||||
icon?: string
|
||||
fontIcon?: string
|
||||
hasBullet?: boolean
|
||||
menuIsOpen?: boolean
|
||||
}
|
||||
|
||||
const SidebarMenuItemWithSub: React.FC<Props & WithChildren> = ({
|
||||
@@ -19,9 +20,10 @@ const SidebarMenuItemWithSub: React.FC<Props & WithChildren> = ({
|
||||
icon,
|
||||
fontIcon,
|
||||
hasBullet,
|
||||
menuIsOpen=false
|
||||
}) => {
|
||||
const {pathname} = useLocation()
|
||||
const isActive = checkIsActive(pathname, to)
|
||||
const isActive = checkIsActive(pathname, to) || menuIsOpen
|
||||
const {config} = useLayout()
|
||||
const {app} = config
|
||||
|
||||
|
||||
@@ -15,18 +15,19 @@ const SidebarMenuMain = () => {
|
||||
fontIcon='bi-app-indicator'
|
||||
/>
|
||||
{/*<SidebarMenuItem to='/builder' icon='switch' title='Layout Builder' fontIcon='bi-layers' />*/}
|
||||
<div className='menu-item'>
|
||||
{/* <div className='menu-item'>
|
||||
<div className='menu-content pt-8 pb-2'>
|
||||
<span className='menu-section text-muted text-uppercase fs-8 ls-1'>Loan</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<SidebarMenuItemWithSub
|
||||
to='/loan/pages'
|
||||
title='Process'
|
||||
fontIcon='bi-archive'
|
||||
icon='element-plus'
|
||||
menuIsOpen={true}
|
||||
>
|
||||
<SidebarMenuItemWithSub to='/loan/pages/process' title='Loan' hasBullet={true}>
|
||||
<SidebarMenuItemWithSub to='/loan/pages/process' title='Loan' hasBullet={true} menuIsOpen={true}>
|
||||
<SidebarMenuItem to='/loan/pages/process/started' title='Started' hasBullet={true} />
|
||||
<SidebarMenuItem to='/loan/pages/process/pending' title='Pending' hasBullet={true} />
|
||||
<SidebarMenuItem
|
||||
@@ -34,6 +35,7 @@ const SidebarMenuMain = () => {
|
||||
title='Ready'
|
||||
hasBullet={true}
|
||||
/>
|
||||
<SidebarMenuItem to='/loan/pages/process/verified' title='Verified' hasBullet={true} />
|
||||
<SidebarMenuItem
|
||||
to='/loan/pages/process/approved'
|
||||
title='Approved'
|
||||
|
||||
@@ -47,6 +47,53 @@ export function postAuxEnd(uri:string, reqData:any):Promise<any> {
|
||||
});
|
||||
}
|
||||
|
||||
export function patchAuxEnd(uri:string, reqData:any):Promise<any> {
|
||||
const endPoint = import.meta.env.VITE_APP_USER_ENDPOINT + uri;
|
||||
const formData = new FormData();
|
||||
for (let value in reqData) {
|
||||
formData.append(value, reqData[value]);
|
||||
}
|
||||
return axios.patch(endPoint, formData)
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
// if (response.data.internal_return == "-9999") {
|
||||
// localStorage.clear();
|
||||
// window.location.href = `/login?sessionExpired=true`;
|
||||
// }
|
||||
return response;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
//response status is an error code
|
||||
console.log(
|
||||
"ERROR-------------------------------------------------------"
|
||||
);
|
||||
console.log(error.response.status);
|
||||
console.log(
|
||||
"ERROR-------------------------------------------------------"
|
||||
);
|
||||
} else if (error.request) {
|
||||
//response not received though the request was sent
|
||||
console.log(
|
||||
"ERROR2-------------------------------------------------------"
|
||||
);
|
||||
console.log(error?.request);
|
||||
console.log(
|
||||
"ERROR2-------------------------------------------------------"
|
||||
);
|
||||
} else {
|
||||
//an error occurred when setting up the request
|
||||
console.log(
|
||||
"ERROR3-------------------------------------------------------"
|
||||
);
|
||||
console.log(error);
|
||||
console.log(
|
||||
"ERROR3-------------------------------------------------------"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function getAuxEnd(uri: string, reqData?: any): Promise<any> {
|
||||
const endPoint = import.meta.env.VITE_APP_USER_ENDPOINT + uri;
|
||||
|
||||
@@ -7,11 +7,14 @@ import {UserEditModal} from './user-edit-modal/UserEditModal'
|
||||
import {KTCard} from '../../../../../_digifi/helpers'
|
||||
import { ToolbarWrapper } from '../../../../../_digifi/layout/components/toolbar'
|
||||
import { Content } from '../../../../../_digifi/layout/components/content'
|
||||
import { useCustomModal } from '../../../../../context/CustomModal'
|
||||
import { Modal } from './edit-signatory-modal/Modal'
|
||||
|
||||
const UsersList = () => {
|
||||
const response = useAllResponse()
|
||||
// console.log('RESPONSE', response)
|
||||
const {itemIdForUpdate} = useListView()
|
||||
const {MODALNAMES, showCustomModal} = useCustomModal()
|
||||
return (
|
||||
<>
|
||||
<KTCard>
|
||||
@@ -19,6 +22,7 @@ const UsersList = () => {
|
||||
<UsersTable />
|
||||
</KTCard>
|
||||
{itemIdForUpdate !== undefined && <UserEditModal />}
|
||||
{(showCustomModal && showCustomModal.name == MODALNAMES.editSignatory) && <Modal />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ export type User = {
|
||||
employer_name?: string
|
||||
title?: string
|
||||
phone?: string
|
||||
employer_id?: string
|
||||
signatory_uid?: string
|
||||
}
|
||||
|
||||
export type UsersQueryResponse = Response<Array<User>>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { ID, Response } from "../../../../../../_digifi/helpers";
|
||||
import { User, UsersQueryResponse } from "./_models";
|
||||
import { patchAuxEnd } from "../../../../auth/core/AxiosCallHelper";
|
||||
|
||||
const API_URL = import.meta.env.VITE_APP_THEME_API_URL;
|
||||
const USER_URL = `${API_URL}/user`;
|
||||
@@ -13,12 +14,16 @@ const NEW_USER_ENDPOINT = import.meta.env.VITE_APP_USER_ENDPOINT
|
||||
// .get(`${GET_USERS_URL}?${query}`)
|
||||
// .then((d: AxiosResponse<UsersQueryResponse>) => d.data);
|
||||
// };
|
||||
const getSignatoryList = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET EMPLOYERS LIST
|
||||
const getSignatoryList = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET EMPLOYERS SIGNATORY LIST
|
||||
return axios
|
||||
.get(`${NEW_USER_ENDPOINT}/employers/signatory`)
|
||||
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
|
||||
};
|
||||
|
||||
const updateUser = (user: User): Promise<User | undefined> => { // FUNCTION TO UPDATE EMPLOYERS SIGNATORY
|
||||
return patchAuxEnd('/employers/signatory', user)
|
||||
};
|
||||
|
||||
const getUserById = (id: ID): Promise<User | undefined> => {
|
||||
return axios
|
||||
.get(`${USER_URL}/${id}`)
|
||||
@@ -33,12 +38,6 @@ const createUser = (user: User): Promise<User | undefined> => {
|
||||
.then((response: Response<User>) => response.data);
|
||||
};
|
||||
|
||||
const updateUser = (user: User): Promise<User | undefined> => {
|
||||
return axios
|
||||
.post(`${USER_URL}/${user.id}`, user)
|
||||
.then((response: AxiosResponse<Response<User>>) => response.data)
|
||||
.then((response: Response<User>) => response.data);
|
||||
};
|
||||
|
||||
const deleteUser = (userId: ID): Promise<void> => {
|
||||
return axios.delete(`${USER_URL}/${userId}`).then(() => {});
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import {useEffect} from 'react'
|
||||
import { ModalHeader } from './ModalHeader'
|
||||
import { ModalFormWrapper } from './ModalFormWrapper'
|
||||
|
||||
|
||||
const Modal = () => {
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add('modal-open')
|
||||
return () => {
|
||||
document.body.classList.remove('modal-open')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='modal fade show d-block'
|
||||
id='kt_modal_add_user'
|
||||
role='dialog'
|
||||
tabIndex={-1}
|
||||
aria-modal='true'
|
||||
>
|
||||
{/* begin::Modal dialog */}
|
||||
<div className='modal-dialog modal-dialog-centered mw-650px'>
|
||||
{/* begin::Modal content */}
|
||||
<div className='modal-content'>
|
||||
<ModalHeader />
|
||||
{/* begin::Modal body */}
|
||||
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
|
||||
<ModalFormWrapper />
|
||||
</div>
|
||||
{/* end::Modal body */}
|
||||
</div>
|
||||
{/* end::Modal content */}
|
||||
</div>
|
||||
{/* end::Modal dialog */}
|
||||
</div>
|
||||
{/* begin::Modal Backdrop */}
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
{/* end::Modal Backdrop */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {Modal}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
import { FC, useState } from "react";
|
||||
import * as Yup from "yup";
|
||||
import { useFormik } from "formik";
|
||||
import { isNotEmpty, toAbsoluteUrl } from "../../../../../../_digifi/helpers";
|
||||
// import { initialUser, User } from "../core/_models";
|
||||
import { initialUser, User } from "../../signatory-list/core/_models";
|
||||
import clsx from "clsx";
|
||||
import { useListView } from "../core/ListViewProvider";
|
||||
import { UsersListLoading } from "../components/loading/UsersListLoading";
|
||||
import { updateUser } from "../core/_requests";
|
||||
import { useQueryResponse } from "../core/QueryResponseProvider";
|
||||
import { useCustomModal } from "../../../../../../context/CustomModal";
|
||||
|
||||
type Props = {
|
||||
isUserLoading: boolean;
|
||||
user: User;
|
||||
};
|
||||
|
||||
const editUserSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email('Wrong email format')
|
||||
.min(3, 'Minimum 3 symbols')
|
||||
.max(50, 'Maximum 50 symbols')
|
||||
.required('required'),
|
||||
name: Yup.string()
|
||||
.min(3, "Minimum 3 symbols")
|
||||
.max(50, "Maximum 50 symbols")
|
||||
.required("required"),
|
||||
phone: Yup.string()
|
||||
.min(11, "Minimum 11 symbols")
|
||||
.max(11, "Maximum 11 symbols")
|
||||
.required("required"),
|
||||
title: Yup.string()
|
||||
.min(2, "Minimum 2 symbols")
|
||||
.max(20, "Maximum 20 symbols")
|
||||
.required("required"),
|
||||
});
|
||||
|
||||
const ModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
|
||||
const {closeCustomModal, showCustomModal} = useCustomModal()
|
||||
|
||||
const { setItemIdForUpdate } = useListView();
|
||||
const { refetch, isLoading } = useQueryResponse();
|
||||
|
||||
const [userForEdit] = useState<User>({
|
||||
...user,
|
||||
avatar: user.avatar || initialUser.avatar,
|
||||
// role: user.role || initialUser.role,
|
||||
// position: user.position || initialUser.position,
|
||||
name: user.name || initialUser.name,
|
||||
// email: user.email || initialUser.email,
|
||||
});
|
||||
|
||||
const cancel = (withRefresh?: boolean) => {
|
||||
if (withRefresh) {
|
||||
refetch();
|
||||
}
|
||||
setItemIdForUpdate(undefined);
|
||||
closeCustomModal()
|
||||
};
|
||||
|
||||
const blankImg = toAbsoluteUrl("media/svg/avatars/blank.svg");
|
||||
const userAvatarImg = toAbsoluteUrl(`media/${userForEdit.avatar}`);
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: userForEdit,
|
||||
validationSchema: editUserSchema,
|
||||
onSubmit: async (values, { setSubmitting }) => {
|
||||
let reqData = {
|
||||
employer_id: values.employer_id,
|
||||
signatory_uid: values.uid,
|
||||
name: values.name,
|
||||
email: values.email,
|
||||
title: values.title,
|
||||
phone: values.phone
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await updateUser({...reqData});
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
} finally {
|
||||
setSubmitting(true);
|
||||
cancel(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
id="kt_modal_add_user_form"
|
||||
className="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
>
|
||||
{/* begin::Scroll */}
|
||||
<div
|
||||
className="d-flex flex-column scroll-y me-n7 pe-7"
|
||||
id="kt_modal_add_user_scroll"
|
||||
data-kt-scroll="true"
|
||||
data-kt-scroll-activate="{default: false, lg: true}"
|
||||
data-kt-scroll-max-height="auto"
|
||||
data-kt-scroll-dependencies="#kt_modal_add_user_header"
|
||||
data-kt-scroll-wrappers="#kt_modal_add_user_scroll"
|
||||
data-kt-scroll-offset="300px"
|
||||
>
|
||||
{/* begin::Input group */}
|
||||
{/* <div className='fv-row mb-7'>
|
||||
|
||||
<label className='d-block fw-bold fs-6 mb-5'>Avatar</label>
|
||||
|
||||
<div
|
||||
className='image-input image-input-outline'
|
||||
data-kt-image-input='true'
|
||||
style={{backgroundImage: `url('${blankImg}')`}}
|
||||
>
|
||||
<div
|
||||
className='image-input-wrapper w-125px h-125px'
|
||||
style={{backgroundImage: `url('${userAvatarImg}')`}}
|
||||
></div>
|
||||
|
||||
<label
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='change'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Change avatar'
|
||||
>
|
||||
<i className='bi bi-pencil-fill fs-7'></i>
|
||||
|
||||
<input type='file' name='avatar' accept='.png, .jpg, .jpeg' />
|
||||
<input type='hidden' name='avatar_remove' />
|
||||
</label>
|
||||
|
||||
<span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='cancel'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Cancel avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span>
|
||||
|
||||
<span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='remove'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Remove avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className='form-text'>Allowed file types: png, jpg, jpeg.</div>
|
||||
</div> */}
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Name</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Full name"
|
||||
{...formik.getFieldProps("name")}
|
||||
type="text"
|
||||
name="name"
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.name && formik.errors.name },
|
||||
{
|
||||
"is-valid": formik.touched.name && !formik.errors.name,
|
||||
}
|
||||
)}
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* end::Input */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Title</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="title"
|
||||
{...formik.getFieldProps("title")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.title && formik.errors.title },
|
||||
{
|
||||
"is-valid": formik.touched.title && !formik.errors.title,
|
||||
}
|
||||
)}
|
||||
type="text"
|
||||
name="title"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.title && formik.errors.title && (
|
||||
// <div className="fv-plugins-message-container">
|
||||
// <span role="alert">{formik.errors.title}</span>
|
||||
// </div>
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Phone</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Phone"
|
||||
{...formik.getFieldProps("phone")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.title && formik.errors.title },
|
||||
{
|
||||
"is-valid": formik.touched.title && !formik.errors.title,
|
||||
}
|
||||
)}
|
||||
type="text"
|
||||
name="phone"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.phone && formik.errors.phone && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.phone}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Email</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Email"
|
||||
{...formik.getFieldProps("email")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.email && formik.errors.email },
|
||||
{
|
||||
"is-valid": formik.touched.email && !formik.errors.email,
|
||||
}
|
||||
)}
|
||||
type="email"
|
||||
name="email"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
|
||||
</div>
|
||||
{/* end::Scroll */}
|
||||
|
||||
{/* begin::Actions */}
|
||||
<div className="text-center pt-15">
|
||||
<button
|
||||
type="reset"
|
||||
onClick={() => cancel()}
|
||||
className="btn btn-danger me-3"
|
||||
data-kt-users-modal-action="cancel"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
data-kt-users-modal-action="submit"
|
||||
disabled={
|
||||
isUserLoading ||
|
||||
formik.isSubmitting ||
|
||||
!formik.isValid ||
|
||||
!formik.touched
|
||||
}
|
||||
>
|
||||
<span className="indicator-label">Update</span>
|
||||
{(formik.isSubmitting || isUserLoading) && (
|
||||
<span className="indicator-progress">
|
||||
Please wait...{" "}
|
||||
<span className="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Actions */}
|
||||
</form>
|
||||
{(formik.isSubmitting || isUserLoading) && <UsersListLoading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { ModalForm };
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import {useQuery} from 'react-query'
|
||||
import {ModalForm} from './ModalForm'
|
||||
import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
import {getUserById} from '../core/_requests'
|
||||
import { useCustomModal } from '../../../../../../context/CustomModal'
|
||||
|
||||
const ModalFormWrapper = () => {
|
||||
const {itemIdForUpdate, setItemIdForUpdate} = useListView()
|
||||
const enabledQuery: boolean = isNotEmpty(itemIdForUpdate)
|
||||
|
||||
const {showCustomModal} = useCustomModal()
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
data: user,
|
||||
error,
|
||||
} = useQuery(
|
||||
`${QUERIES.USERS_LIST}-user-${itemIdForUpdate}`,
|
||||
() => {
|
||||
return getUserById(itemIdForUpdate)
|
||||
},
|
||||
{
|
||||
cacheTime: 0,
|
||||
enabled: enabledQuery,
|
||||
onError: (err) => {
|
||||
setItemIdForUpdate(undefined)
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return <ModalForm isUserLoading={isLoading} user={{...showCustomModal.data}} />
|
||||
}
|
||||
|
||||
export {ModalFormWrapper}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import {KTIcon} from '../../../../../../_digifi/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
import { useCustomModal } from '../../../../../../context/CustomModal'
|
||||
|
||||
const ModalHeader = () => {
|
||||
const {setItemIdForUpdate, itemIdForUpdate} = useListView()
|
||||
const {closeCustomModal, showCustomModal} = useCustomModal()
|
||||
|
||||
const onClose = () => {
|
||||
setItemIdForUpdate(undefined)
|
||||
closeCustomModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-header'>
|
||||
{/* begin::Modal title */}
|
||||
<h2 className='fw-bolder'>Edit Signatory - {showCustomModal?.data?.employer_name}</h2>
|
||||
{/* end::Modal title */}
|
||||
|
||||
{/* begin::Close */}
|
||||
<div
|
||||
className='btn btn-icon btn-sm btn-active-icon-primary'
|
||||
data-kt-users-modal-action='close'
|
||||
onClick={onClose}
|
||||
style={{cursor: 'pointer'}}
|
||||
>
|
||||
<KTIcon iconName='cross' className='fs-1' />
|
||||
</div>
|
||||
{/* end::Close */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {ModalHeader}
|
||||
+10
-2
@@ -7,14 +7,22 @@ import {useListView} from '../../core/ListViewProvider'
|
||||
import {useQueryResponse} from '../../core/QueryResponseProvider'
|
||||
import {deleteUser} from '../../core/_requests'
|
||||
|
||||
import { useCustomModal } from '../../../../../../../context/CustomModal'
|
||||
import { User } from '../../core/_models'
|
||||
|
||||
|
||||
type Props = {
|
||||
id: ID
|
||||
data: Array<User> | any
|
||||
}
|
||||
|
||||
const UserActionsCell: FC<Props> = ({id}) => {
|
||||
const UserActionsCell: FC<Props> = ({id, data}) => {
|
||||
const {setItemIdForUpdate} = useListView()
|
||||
const {query} = useQueryResponse()
|
||||
const queryClient = useQueryClient()
|
||||
const {MODALNAMES, openCustomModal} = useCustomModal()
|
||||
|
||||
const selectedUser:User = data.filter((item:User) => item?.uid == id)[0]
|
||||
|
||||
useEffect(() => {
|
||||
MenuComponent.reinitialization()
|
||||
@@ -50,7 +58,7 @@ const UserActionsCell: FC<Props> = ({id}) => {
|
||||
>
|
||||
{/* begin::Menu item */}
|
||||
<div className='menu-item px-3'>
|
||||
<a className='menu-link px-3' onClick={openEditModal}>
|
||||
<a className='menu-link px-3' onClick={()=>{openCustomModal(MODALNAMES.editSignatory, {...selectedUser})}}>
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@ const usersColumns: ReadonlyArray<Column<User>> = [
|
||||
<UserCustomHeader tableProps={props} title='Actions' className='text-end min-w-100px' />
|
||||
),
|
||||
id: 'actions',
|
||||
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].uid} />,
|
||||
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].uid} data={props.data} />,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ import {QueryRequestProvider} from './core/QueryRequestProvider'
|
||||
import {QueryResponseProvider} from './core/QueryResponseProvider'
|
||||
import {UsersListHeader} from './components/header/UsersListHeader'
|
||||
import {UsersTable} from './table/UsersTable'
|
||||
import {UserEditModal} from './user-edit-modal/UserEditModal'
|
||||
import {AddEmployerModal} from './add-employer-modal/AddEmployerModal'
|
||||
import {KTCard} from '../../../../../_digifi/helpers'
|
||||
import { ToolbarWrapper } from '../../../../../_digifi/layout/components/toolbar'
|
||||
import { Content } from '../../../../../_digifi/layout/components/content'
|
||||
import { AddSignatoryModal } from './add-signatory-modal/Modal'
|
||||
import { useCustomModal } from '../../../../../context/CustomModal'
|
||||
import { EmployerEditModal } from './employer-edit-modal/EmployerEditModal'
|
||||
|
||||
const UsersList = () => {
|
||||
const {itemIdForUpdate} = useListView()
|
||||
@@ -20,8 +21,9 @@ const UsersList = () => {
|
||||
<UsersListHeader />
|
||||
<UsersTable />
|
||||
</KTCard>
|
||||
{itemIdForUpdate !== undefined && <UserEditModal />}
|
||||
{itemIdForUpdate !== undefined && <AddEmployerModal />}
|
||||
{(showCustomModal && showCustomModal.name == MODALNAMES.addSignatory) && <AddSignatoryModal />}
|
||||
{(showCustomModal && showCustomModal.name == MODALNAMES.editEmployer) && <EmployerEditModal />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import {useEffect} from 'react'
|
||||
import {ModalHeader} from './ModalHeader'
|
||||
import {ModalFormWrapper} from './ModalFormWrapper'
|
||||
|
||||
|
||||
const AddEmployerModal = () => {
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add('modal-open')
|
||||
return () => {
|
||||
document.body.classList.remove('modal-open')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='modal fade show d-block'
|
||||
id='kt_modal_add_user'
|
||||
role='dialog'
|
||||
tabIndex={-1}
|
||||
aria-modal='true'
|
||||
>
|
||||
{/* begin::Modal dialog */}
|
||||
<div className='modal-dialog modal-dialog-centered mw-650px'>
|
||||
{/* begin::Modal content */}
|
||||
<div className='modal-content'>
|
||||
<ModalHeader />
|
||||
{/* begin::Modal body */}
|
||||
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
|
||||
<ModalFormWrapper />
|
||||
</div>
|
||||
{/* end::Modal body */}
|
||||
</div>
|
||||
{/* end::Modal content */}
|
||||
</div>
|
||||
{/* end::Modal dialog */}
|
||||
</div>
|
||||
{/* begin::Modal Backdrop */}
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
{/* end::Modal Backdrop */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {AddEmployerModal}
|
||||
+11
-6
@@ -14,6 +14,11 @@ type Props = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
uid?: string
|
||||
name?: string
|
||||
};
|
||||
|
||||
const editUserSchema = Yup.object().shape({
|
||||
// email: Yup.string()
|
||||
// .email('Wrong email format')
|
||||
@@ -52,7 +57,7 @@ const editUserSchema = Yup.object().shape({
|
||||
.required("required"),
|
||||
});
|
||||
|
||||
const UserEditModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
const ModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
const response:any = useAllResponse()
|
||||
|
||||
const { setItemIdForUpdate } = useListView();
|
||||
@@ -354,8 +359,8 @@ const UserEditModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
:
|
||||
<>
|
||||
<option value=''>Select Sector</option>
|
||||
{response?.employer_sector?.map((item:any) => (
|
||||
<option value={item.uid}>{item.name}</option>
|
||||
{response?.employer_sector?.map((item:SelectProps) => (
|
||||
<option key={item.uid} value={item.uid}>{item.name}</option>
|
||||
))
|
||||
}
|
||||
</>
|
||||
@@ -397,8 +402,8 @@ const UserEditModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
:
|
||||
<>
|
||||
<option value=''>Select Salary Source</option>
|
||||
{response?.salary_sources?.map((item:any) => (
|
||||
<option value={item.uid}>{item.name}</option>
|
||||
{response?.salary_sources?.map((item:SelectProps) => (
|
||||
<option key={item.uid} value={item.uid}>{item.name}</option>
|
||||
))
|
||||
}
|
||||
</>
|
||||
@@ -457,4 +462,4 @@ const UserEditModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export { UserEditModalForm };
|
||||
export { ModalForm };
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
import {useQuery} from 'react-query'
|
||||
import {UserEditModalForm} from './UserEditModalForm'
|
||||
import {ModalForm} from './ModalForm'
|
||||
import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
import {getUserById} from '../core/_requests'
|
||||
|
||||
const UserEditModalFormWrapper = () => {
|
||||
const ModalFormWrapper = () => {
|
||||
const {itemIdForUpdate, setItemIdForUpdate} = useListView()
|
||||
const enabledQuery: boolean = isNotEmpty(itemIdForUpdate)
|
||||
const {
|
||||
@@ -27,14 +27,14 @@ const UserEditModalFormWrapper = () => {
|
||||
)
|
||||
|
||||
if (!itemIdForUpdate) {
|
||||
return <UserEditModalForm isUserLoading={isLoading} user={{id: undefined}} />
|
||||
return <ModalForm isUserLoading={isLoading} user={{id: undefined}} />
|
||||
}
|
||||
|
||||
if (!isLoading && !error && user) {
|
||||
return <UserEditModalForm isUserLoading={isLoading} user={user} />
|
||||
return <ModalForm isUserLoading={isLoading} user={user} />
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export {UserEditModalFormWrapper}
|
||||
export {ModalFormWrapper}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import {KTIcon} from '../../../../../../_digifi/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
|
||||
const UserEditModalHeader = () => {
|
||||
const ModalHeader = () => {
|
||||
const {setItemIdForUpdate, itemIdForUpdate} = useListView()
|
||||
|
||||
return (
|
||||
@@ -24,4 +24,4 @@ const UserEditModalHeader = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export {UserEditModalHeader}
|
||||
export {ModalHeader}
|
||||
@@ -23,6 +23,7 @@ export type User = {
|
||||
sector?: string
|
||||
salary_source?: string
|
||||
added?: string
|
||||
signatory_count?: string
|
||||
updated?: string
|
||||
email?: string
|
||||
}
|
||||
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
import { FC, useState } from "react";
|
||||
import * as Yup from "yup";
|
||||
import { useFormik } from "formik";
|
||||
import { isNotEmpty, toAbsoluteUrl } from "../../../../../../_digifi/helpers";
|
||||
import { initialUser, User } from "../core/_models";
|
||||
import clsx from "clsx";
|
||||
import { useListView } from "../core/ListViewProvider";
|
||||
import { UsersListLoading } from "../components/loading/UsersListLoading";
|
||||
import { createUser, updateUser } from "../core/_requests";
|
||||
import { useAllResponse, useQueryResponse } from "../core/QueryResponseProvider";
|
||||
import { useCustomModal } from "../../../../../../context/CustomModal";
|
||||
|
||||
type Props = {
|
||||
isUserLoading: boolean;
|
||||
user: User;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
uid?: string
|
||||
name?: string
|
||||
};
|
||||
|
||||
const editUserSchema = Yup.object().shape({
|
||||
// email: Yup.string()
|
||||
// .email('Wrong email format')
|
||||
// .min(3, 'Minimum 3 symbols')
|
||||
// .max(50, 'Maximum 50 symbols')
|
||||
// .required('required'),
|
||||
name: Yup.string()
|
||||
.min(3, "Minimum 3 symbols")
|
||||
.max(50, "Maximum 50 symbols")
|
||||
.required("required"),
|
||||
percent_interest: Yup.number()
|
||||
.typeError("Invalid number")
|
||||
.min(1, "must be greater than 0")
|
||||
// .test("no-e", "Invalid number", (value) => {
|
||||
// if (value && /\d+e/.test(value)) {
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
// })
|
||||
.required("required"),
|
||||
max_loan: Yup.number()
|
||||
.typeError("Invalid number")
|
||||
.min(1, "must be greater than 0")
|
||||
.required("required"),
|
||||
tenor: Yup.number()
|
||||
.typeError("Invalid number")
|
||||
.min(1, "must be greater than 0")
|
||||
.required("required"),
|
||||
retirement_age: Yup.number()
|
||||
.typeError("Invalid number")
|
||||
.min(1, "must be greater than 0")
|
||||
.required("is required"),
|
||||
sector: Yup.string()
|
||||
.required("required"),
|
||||
salary_source: Yup.string()
|
||||
.required("required"),
|
||||
});
|
||||
|
||||
const EditModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
const response:any = useAllResponse()
|
||||
|
||||
const { setItemIdForUpdate } = useListView();
|
||||
const { refetch, isLoading } = useQueryResponse();
|
||||
|
||||
const {closeCustomModal, showCustomModal} = useCustomModal()
|
||||
|
||||
const [userForEdit] = useState<User>({
|
||||
...user,
|
||||
avatar: user.avatar || initialUser.avatar,
|
||||
// role: user.role || initialUser.role,
|
||||
// position: user.position || initialUser.position,
|
||||
name: user.name || initialUser.name,
|
||||
// email: user.email || initialUser.email,
|
||||
});
|
||||
|
||||
const cancel = (withRefresh?: boolean) => {
|
||||
if (withRefresh) {
|
||||
refetch();
|
||||
}
|
||||
setItemIdForUpdate(undefined);
|
||||
closeCustomModal()
|
||||
};
|
||||
|
||||
const blankImg = toAbsoluteUrl("media/svg/avatars/blank.svg");
|
||||
const userAvatarImg = toAbsoluteUrl(`media/${userForEdit.avatar}`);
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: userForEdit,
|
||||
validationSchema: editUserSchema,
|
||||
onSubmit: async (values, { setSubmitting }) => {
|
||||
// setSubmitting(true);
|
||||
// try {
|
||||
// if (isNotEmpty(values.id)) {
|
||||
// await updateUser(values);
|
||||
// } else {
|
||||
// await createUser(values);
|
||||
// }
|
||||
// } catch (ex) {
|
||||
// console.error(ex);
|
||||
// } finally {
|
||||
// setSubmitting(true);
|
||||
// cancel(true);
|
||||
// }
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
id="kt_modal_add_user_form"
|
||||
className="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
>
|
||||
{/* begin::Scroll */}
|
||||
<div
|
||||
className="d-flex flex-column scroll-y me-n7 pe-7"
|
||||
id="kt_modal_add_user_scroll"
|
||||
data-kt-scroll="true"
|
||||
data-kt-scroll-activate="{default: false, lg: true}"
|
||||
data-kt-scroll-max-height="auto"
|
||||
data-kt-scroll-dependencies="#kt_modal_add_user_header"
|
||||
data-kt-scroll-wrappers="#kt_modal_add_user_scroll"
|
||||
data-kt-scroll-offset="300px"
|
||||
>
|
||||
{/* begin::Input group */}
|
||||
{/* <div className='fv-row mb-7'>
|
||||
|
||||
<label className='d-block fw-bold fs-6 mb-5'>Avatar</label>
|
||||
|
||||
<div
|
||||
className='image-input image-input-outline'
|
||||
data-kt-image-input='true'
|
||||
style={{backgroundImage: `url('${blankImg}')`}}
|
||||
>
|
||||
<div
|
||||
className='image-input-wrapper w-125px h-125px'
|
||||
style={{backgroundImage: `url('${userAvatarImg}')`}}
|
||||
></div>
|
||||
|
||||
<label
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='change'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Change avatar'
|
||||
>
|
||||
<i className='bi bi-pencil-fill fs-7'></i>
|
||||
|
||||
<input type='file' name='avatar' accept='.png, .jpg, .jpeg' />
|
||||
<input type='hidden' name='avatar_remove' />
|
||||
</label>
|
||||
|
||||
<span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='cancel'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Cancel avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span>
|
||||
|
||||
<span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='remove'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Remove avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className='form-text'>Allowed file types: png, jpg, jpeg.</div>
|
||||
</div> */}
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Full Name</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Full name"
|
||||
{...formik.getFieldProps("name")}
|
||||
type="text"
|
||||
name="name"
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.name && formik.errors.name },
|
||||
{
|
||||
"is-valid": formik.touched.name && !formik.errors.name,
|
||||
}
|
||||
)}
|
||||
autoComplete="off"
|
||||
// disabled={formik.isSubmitting || isUserLoading}
|
||||
disabled={true}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* end::Input */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Interest</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Interest"
|
||||
{...formik.getFieldProps("percent_interest")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.percent_interest && formik.errors.percent_interest },
|
||||
{
|
||||
"is-valid": formik.touched.percent_interest && !formik.errors.percent_interest,
|
||||
}
|
||||
)}
|
||||
type="text"
|
||||
name="percent_interest"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.percent_interest && formik.errors.percent_interest && (
|
||||
// <div className="fv-plugins-message-container">
|
||||
// <span role="alert">{formik.errors.percent_interest}</span>
|
||||
// </div>
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.percent_interest}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Max Loan</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Max Loan"
|
||||
{...formik.getFieldProps("max_loan")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.max_loan && formik.errors.max_loan },
|
||||
{
|
||||
"is-valid": formik.touched.max_loan && !formik.errors.max_loan,
|
||||
}
|
||||
)}
|
||||
type="text"
|
||||
name="max_loan"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.max_loan && formik.errors.max_loan && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.max_loan}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Tenor</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Tenor"
|
||||
{...formik.getFieldProps("tenor")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.tenor && formik.errors.tenor },
|
||||
{
|
||||
"is-valid": formik.touched.tenor && !formik.errors.tenor,
|
||||
}
|
||||
)}
|
||||
type="text"
|
||||
name="tenor"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.tenor && formik.errors.tenor && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.tenor}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Ret Age</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Retirement Age"
|
||||
{...formik.getFieldProps("retirement_age")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.retirement_age && formik.errors.retirement_age },
|
||||
{
|
||||
"is-valid": formik.touched.retirement_age && !formik.errors.retirement_age,
|
||||
}
|
||||
)}
|
||||
type="text"
|
||||
name="retirement_age"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.retirement_age && formik.errors.retirement_age && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.retirement_age}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
<label className="required fw-bold fs-6 mb-2">Sector</label>
|
||||
<select
|
||||
{...formik.getFieldProps("sector")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.sector && formik.errors.sector },
|
||||
{
|
||||
"is-valid": formik.touched.sector && !formik.errors.sector,
|
||||
}
|
||||
)}
|
||||
name="sector"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
>
|
||||
{isLoading ?
|
||||
<option value=''>Loading...</option>
|
||||
:
|
||||
<>
|
||||
{response?.employer_sector?.map((item:SelectProps) => (
|
||||
<option key={item.uid} value={item.uid}>{item.name}</option>
|
||||
))
|
||||
}
|
||||
</>
|
||||
}
|
||||
</select>
|
||||
{/* end::Input */}
|
||||
{formik.touched.sector && formik.errors.sector && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.sector}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Salary</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<select
|
||||
{...formik.getFieldProps("salary_source")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.salary_source && formik.errors.salary_source },
|
||||
{
|
||||
"is-valid": formik.touched.salary_source && !formik.errors.salary_source,
|
||||
}
|
||||
)}
|
||||
name="salary_source"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
>
|
||||
{isLoading ?
|
||||
<option value=''>Loading...</option>
|
||||
:
|
||||
<>
|
||||
{response?.salary_sources?.map((item:SelectProps) => (
|
||||
<option key={item.uid} value={item.uid}>{item.name}</option>
|
||||
))
|
||||
}
|
||||
</>
|
||||
}
|
||||
</select>
|
||||
{/* end::Input */}
|
||||
{formik.touched.salary_source && formik.errors.salary_source && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.salary_source}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
</div>
|
||||
{/* end::Scroll */}
|
||||
|
||||
{/* begin::Actions */}
|
||||
<div className="text-center pt-15">
|
||||
<button
|
||||
type="reset"
|
||||
onClick={() => cancel()}
|
||||
className="btn btn-danger me-3"
|
||||
data-kt-users-modal-action="cancel"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
data-kt-users-modal-action="submit"
|
||||
disabled={
|
||||
isUserLoading ||
|
||||
formik.isSubmitting ||
|
||||
!formik.isValid ||
|
||||
!formik.touched
|
||||
}
|
||||
>
|
||||
<span className="indicator-label">Update</span>
|
||||
{(formik.isSubmitting || isUserLoading) && (
|
||||
<span className="indicator-progress">
|
||||
Please wait...{" "}
|
||||
<span className="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Actions */}
|
||||
</form>
|
||||
{(formik.isSubmitting || isUserLoading) && <UsersListLoading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditModalForm };
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import {useQuery} from 'react-query'
|
||||
import {EditModalForm} from './EditModalForm'
|
||||
import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
import {getUserById} from '../core/_requests'
|
||||
import { useCustomModal } from '../../../../../../context/CustomModal'
|
||||
|
||||
const EditModalFormWrapper = () => {
|
||||
const {itemIdForUpdate, setItemIdForUpdate} = useListView()
|
||||
const enabledQuery: boolean = isNotEmpty(itemIdForUpdate)
|
||||
|
||||
const {showCustomModal} = useCustomModal()
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
data: user,
|
||||
error,
|
||||
} = useQuery(
|
||||
`${QUERIES.USERS_LIST}-user-${itemIdForUpdate}`,
|
||||
() => {
|
||||
return getUserById(itemIdForUpdate)
|
||||
},
|
||||
{
|
||||
cacheTime: 0,
|
||||
enabled: enabledQuery,
|
||||
onError: (err) => {
|
||||
setItemIdForUpdate(undefined)
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return <EditModalForm isUserLoading={isLoading} user={{...showCustomModal.data}} />
|
||||
}
|
||||
|
||||
export {EditModalFormWrapper}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import {KTIcon} from '../../../../../../_digifi/helpers'
|
||||
import { useCustomModal } from '../../../../../../context/CustomModal'
|
||||
import {useListView} from '../core/ListViewProvider'
|
||||
|
||||
const EditModalHeader = () => {
|
||||
const {setItemIdForUpdate, itemIdForUpdate} = useListView()
|
||||
|
||||
const {closeCustomModal} = useCustomModal()
|
||||
|
||||
const onClose = () => {
|
||||
setItemIdForUpdate(undefined)
|
||||
closeCustomModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-header'>
|
||||
{/* begin::Modal title */}
|
||||
<h2 className='fw-bolder'>Edit Employer</h2>
|
||||
{/* end::Modal title */}
|
||||
|
||||
{/* begin::Close */}
|
||||
<div
|
||||
className='btn btn-icon btn-sm btn-active-icon-primary'
|
||||
data-kt-users-modal-action='close'
|
||||
onClick={onClose}
|
||||
style={{cursor: 'pointer'}}
|
||||
>
|
||||
<KTIcon iconName='cross' className='fs-1' />
|
||||
</div>
|
||||
{/* end::Close */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {EditModalHeader}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import {useEffect} from 'react'
|
||||
import {EditModalHeader} from './EditModalHeader'
|
||||
import {EditModalFormWrapper} from './EditModalFormWrapper'
|
||||
|
||||
|
||||
const EmployerEditModal = () => {
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add('modal-open')
|
||||
return () => {
|
||||
document.body.classList.remove('modal-open')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='modal fade show d-block'
|
||||
id='kt_modal_add_user'
|
||||
role='dialog'
|
||||
tabIndex={-1}
|
||||
aria-modal='true'
|
||||
>
|
||||
{/* begin::Modal dialog */}
|
||||
<div className='modal-dialog modal-dialog-centered mw-650px'>
|
||||
{/* begin::Modal content */}
|
||||
<div className='modal-content'>
|
||||
<EditModalHeader />
|
||||
{/* begin::Modal body */}
|
||||
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
|
||||
<EditModalFormWrapper />
|
||||
</div>
|
||||
{/* end::Modal body */}
|
||||
</div>
|
||||
{/* end::Modal content */}
|
||||
</div>
|
||||
{/* end::Modal dialog */}
|
||||
</div>
|
||||
{/* begin::Modal Backdrop */}
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
{/* end::Modal Backdrop */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {EmployerEditModal}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {FC} from 'react'
|
||||
|
||||
type Props = {
|
||||
signatory_count?: string
|
||||
}
|
||||
|
||||
const SignatoryCount: FC<Props> = ({signatory_count}) => (
|
||||
<> {signatory_count && <div className='badge badge-light fw-bolder'>{signatory_count}</div>}</>
|
||||
)
|
||||
|
||||
export {SignatoryCount}
|
||||
+6
-2
@@ -7,17 +7,21 @@ import {useListView} from '../../core/ListViewProvider'
|
||||
import {useQueryResponse} from '../../core/QueryResponseProvider'
|
||||
import {deleteUser} from '../../core/_requests'
|
||||
import { useCustomModal } from '../../../../../../../context/CustomModal'
|
||||
import { User } from '../../core/_models'
|
||||
|
||||
type Props = {
|
||||
id: ID
|
||||
data: Array<User> | any
|
||||
}
|
||||
|
||||
const UserActionsCell: FC<Props> = ({id}) => {
|
||||
const UserActionsCell: FC<Props> = ({id, data}) => {
|
||||
const {setItemIdForUpdate} = useListView()
|
||||
const {query} = useQueryResponse()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const {MODALNAMES, openCustomModal} = useCustomModal()
|
||||
|
||||
const selectedUser:User = data.filter((item:User) => item?.uid == id)[0]
|
||||
|
||||
useEffect(() => {
|
||||
MenuComponent.reinitialization()
|
||||
@@ -53,7 +57,7 @@ const UserActionsCell: FC<Props> = ({id}) => {
|
||||
>
|
||||
{/* begin::Menu item */}
|
||||
<div className='menu-item px-3'>
|
||||
<a className='menu-link px-3' onClick={openEditModal}>
|
||||
<a className='menu-link px-3' onClick={()=>{openCustomModal(MODALNAMES.editEmployer, {...selectedUser})}}>
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {UserSelectionHeader} from './UserSelectionHeader'
|
||||
import {User} from '../../core/_models'
|
||||
import { AddedCell } from './AddedCell'
|
||||
import { MaxLoan } from './MaxLoan'
|
||||
import { SignatoryCount } from './SignatoryCount'
|
||||
|
||||
const usersColumns: ReadonlyArray<Column<User>> = [
|
||||
{
|
||||
@@ -34,14 +35,14 @@ const usersColumns: ReadonlyArray<Column<User>> = [
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Percent Interest' className='min-w-125px' />
|
||||
<UserCustomHeader tableProps={props} title='% Interest' className='min-w-125px' />
|
||||
),
|
||||
id: 'percent_interest',
|
||||
Cell: ({...props}) => <PaymentMonthCell payment_month={props.data[props.row.index].percent_interest} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Retirement Age' className='min-w-125px' />
|
||||
<UserCustomHeader tableProps={props} title='Ret. Age' className='min-w-125px' />
|
||||
),
|
||||
id: 'retirement_age',
|
||||
Cell: ({...props}) => <AgentCell agent={props.data[props.row.index].retirement_age} />,
|
||||
@@ -53,12 +54,19 @@ const usersColumns: ReadonlyArray<Column<User>> = [
|
||||
id: 'added',
|
||||
Cell: ({...props}) => <AddedCell added={props.data[props.row.index].added} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Sig. No' className='min-w-125px' />
|
||||
),
|
||||
id: 'signatory_count',
|
||||
Cell: ({...props}) => <SignatoryCount signatory_count={props.data[props.row.index].signatory_count} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Actions' className='text-end min-w-100px' />
|
||||
),
|
||||
id: 'actions',
|
||||
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].uid} />,
|
||||
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].uid} data={props.data} />,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UserPendingList } from './components/UserPendingList'
|
||||
import { UserReadyList } from './components/UserReadyList'
|
||||
import { UserApprovedList } from './components/UserApprovedList'
|
||||
import { UserRejectedList } from './components/UserRejectedList'
|
||||
import { UserVerifiedList } from './components/UserVerifiedList'
|
||||
|
||||
const processBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
@@ -64,6 +65,15 @@ const ProcessPage = () => (
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='verified'
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={processBreadCrumbs}>Verified</PageTitle>
|
||||
<UserVerifiedList />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='approved'
|
||||
element={
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {useLocation, useNavigate} from 'react-router-dom'
|
||||
import { Content } from '../../../../_digifi/layout/components/content'
|
||||
import { ToolbarWrapper } from '../../../../_digifi/layout/components/toolbar'
|
||||
import { UsersListLoading } from '../user-started/components/loading/UsersListLoading'
|
||||
|
||||
export default function ApproveRejectPage() {
|
||||
const {state:{selectedUser}} = useLocation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [requestStatus, setRequestStatus] = useState<any>({loading:false, status:false, data:null})
|
||||
|
||||
const handleSubmit = ():any => {
|
||||
setRequestStatus({loading:true, status:false, data:null})
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, data:null})
|
||||
},2000)
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(!selectedUser){
|
||||
navigate('/', {replace:true})
|
||||
}
|
||||
},[])
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ToolbarWrapper /> */}
|
||||
<Content>
|
||||
<div className='w-100'>
|
||||
<h3 className='py-3 py-xl-5 card-title text-gray-800 fw-bold'>Processing: {selectedUser?.uid}</h3>
|
||||
</div>
|
||||
{/* begin::Row */}
|
||||
<div className="row g-5 g-xl-10 mb-5 mb-xl-10">
|
||||
{/* begin::Col */}
|
||||
<div className="col-xl-6 mb-md-5 mb-xl-10">
|
||||
<div className="card card-flash flex flex-col justify-content-between p-4 h-md-50 mb-5 mb-xl-10 bg-secondary">
|
||||
<h3 className='card-title text-gray-800 fw-bold'>Process Loan</h3>
|
||||
<div className='w-100 d-flex justify-content-between'>
|
||||
<button
|
||||
className='btn btn-light btn-active-light-secondary text-success btn-lg'
|
||||
onClick={()=>navigate('/loan/pages/process/verified', {replace:true})}
|
||||
>
|
||||
Return
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-light btn-active-light-secondary text-danger btn-lg'
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-light btn-active-light-secondary text-primary btn-lg'
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Approve
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card-flash flex flex-col justify-content-between p-4 h-md-50 mb-5 mb-xl-10 bg-secondary">
|
||||
<h3 className='card-title text-gray-800 fw-bold'>Verification details</h3>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
|
||||
{/* begin::Col */}
|
||||
<div className="col-xl-6">
|
||||
<div className="card card-flash flex flex-col justify-content-between p-4 h-md-100 bg-secondary">
|
||||
<h3 className='card-title text-gray-800 fw-bold'>Loan Details</h3>
|
||||
</div>
|
||||
</div>
|
||||
{/* end::Col */}
|
||||
</div>
|
||||
{/* end::Row */}
|
||||
</Content>
|
||||
{requestStatus.loading && <UsersListLoading />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Navigate, Routes, Route, Outlet } from "react-router-dom";
|
||||
import { PageLink, PageTitle } from "../../../../_digifi/layout/core";
|
||||
|
||||
import ApproveRejectPage from "./ApproveRejectPage";
|
||||
|
||||
const processBreadCrumbs: Array<PageLink> = [
|
||||
{
|
||||
title: "Loan",
|
||||
path: "/loan/pages/process/verified",
|
||||
isSeparator: false,
|
||||
isActive: false,
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
path: "",
|
||||
isSeparator: true,
|
||||
isActive: false,
|
||||
},
|
||||
];
|
||||
|
||||
const ApproveRejectRoutes = () => (
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<>
|
||||
{/* <ProcessHeader /> */}
|
||||
<Outlet />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="process"
|
||||
element={
|
||||
<>
|
||||
<PageTitle breadcrumbs={processBreadCrumbs}>Verified</PageTitle>
|
||||
<ApproveRejectPage />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route index element={<Navigate to="/loan/verified/process" />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
export default ApproveRejectRoutes;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { UsersListWrapper } from "../user-verified/UsersList";
|
||||
|
||||
const UserVerifiedList = () => <UsersListWrapper />;
|
||||
|
||||
export { UserVerifiedList };
|
||||
@@ -38,6 +38,12 @@ const getReadyUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCT
|
||||
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
|
||||
};
|
||||
|
||||
const getVerifiedUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE ARE VERIFIED
|
||||
return axios
|
||||
.get(`${NEW_USER_ENDPOINT}/loan/verified`)
|
||||
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
|
||||
};
|
||||
|
||||
const getApprovedUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE APPROVED LOAN APPLICATION
|
||||
return axios
|
||||
.get(`${NEW_USER_ENDPOINT}/loan/approved`)
|
||||
@@ -83,6 +89,7 @@ export {
|
||||
getRejectedUsers,
|
||||
getPendingUsers,
|
||||
getReadyUsers,
|
||||
getVerifiedUsers,
|
||||
getApprovedUsers,
|
||||
employersVerify,
|
||||
deleteUser,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ListViewProvider, useListView } from "./core/ListViewProvider";
|
||||
import { QueryRequestProvider } from "./core/QueryRequestProvider";
|
||||
import { QueryResponseProvider } from "./core/QueryResponseProvider";
|
||||
import { UsersListHeader } from "./components/header/UsersListHeader";
|
||||
import { UsersTable } from "./table/UsersTable";
|
||||
import { EditLoanModal } from "./edit-loan-modal/EditLoanModal";
|
||||
import { KTCard } from "../../../../_digifi/helpers";
|
||||
import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar";
|
||||
import { Content } from "../../../../_digifi/layout/components/content";
|
||||
|
||||
const UsersList = () => {
|
||||
const { itemIdForUpdate } = useListView();
|
||||
return (
|
||||
<>
|
||||
<KTCard>
|
||||
<UsersListHeader />
|
||||
<UsersTable />
|
||||
</KTCard>
|
||||
{itemIdForUpdate !== undefined && <EditLoanModal />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const UsersListWrapper = () => (
|
||||
<QueryRequestProvider>
|
||||
<QueryResponseProvider>
|
||||
<ListViewProvider>
|
||||
<ToolbarWrapper />
|
||||
<Content>
|
||||
<UsersList />
|
||||
</Content>
|
||||
</ListViewProvider>
|
||||
</QueryResponseProvider>
|
||||
</QueryRequestProvider>
|
||||
);
|
||||
|
||||
export { UsersListWrapper };
|
||||
@@ -0,0 +1,35 @@
|
||||
import { KTIcon } from "../../../../../../_digifi/helpers";
|
||||
import { useListView } from "../../core/ListViewProvider";
|
||||
import { UsersListFilter } from "./UsersListFilter";
|
||||
|
||||
const UsersListToolbar = () => {
|
||||
const { setItemIdForUpdate } = useListView();
|
||||
const openAddUserModal = () => {
|
||||
setItemIdForUpdate(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="d-flex justify-content-end"
|
||||
data-kt-user-table-toolbar="base"
|
||||
>
|
||||
<UsersListFilter />
|
||||
|
||||
{/* begin::Export */}
|
||||
{/* <button type='button' className='btn btn-light-primary me-3'>
|
||||
<KTIcon iconName='exit-up' className='fs-2' />
|
||||
Export
|
||||
</button> */}
|
||||
{/* end::Export */}
|
||||
|
||||
{/* begin::Add user */}
|
||||
{/* <button type='button' className='btn btn-primary' onClick={openAddUserModal}>
|
||||
<KTIcon iconName='plus' className='fs-2' />
|
||||
Add User
|
||||
</button> */}
|
||||
{/* end::Add user */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { UsersListToolbar };
|
||||
@@ -0,0 +1,136 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { MenuComponent } from "../../../../../../_digifi/assets/ts/components";
|
||||
import { initialQueryState, KTIcon } from "../../../../../../_digifi/helpers";
|
||||
import { useQueryRequest } from "../../core/QueryRequestProvider";
|
||||
import { useQueryResponse } from "../../core/QueryResponseProvider";
|
||||
|
||||
const UsersListFilter = () => {
|
||||
const { updateState } = useQueryRequest();
|
||||
const { isLoading } = useQueryResponse();
|
||||
const [role, setRole] = useState<string | undefined>();
|
||||
const [lastLogin, setLastLogin] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
MenuComponent.reinitialization();
|
||||
}, []);
|
||||
|
||||
const resetData = () => {
|
||||
updateState({ filter: undefined, ...initialQueryState });
|
||||
};
|
||||
|
||||
const filterData = () => {
|
||||
updateState({
|
||||
filter: { role, last_login: lastLogin },
|
||||
...initialQueryState,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* begin::Filter Button */}
|
||||
<button
|
||||
disabled={isLoading}
|
||||
type="button"
|
||||
className="btn btn-light-primary me-3"
|
||||
data-kt-menu-trigger="click"
|
||||
data-kt-menu-placement="bottom-end"
|
||||
>
|
||||
<KTIcon iconName="filter" className="fs-2" />
|
||||
Filter
|
||||
</button>
|
||||
{/* end::Filter Button */}
|
||||
{/* begin::SubMenu */}
|
||||
<div
|
||||
className="menu menu-sub menu-sub-dropdown w-300px w-md-325px"
|
||||
data-kt-menu="true"
|
||||
>
|
||||
{/* begin::Header */}
|
||||
<div className="px-7 py-5">
|
||||
<div className="fs-5 text-gray-900 fw-bolder">Filter Options</div>
|
||||
</div>
|
||||
{/* end::Header */}
|
||||
|
||||
{/* begin::Separator */}
|
||||
<div className="separator border-gray-200"></div>
|
||||
{/* end::Separator */}
|
||||
|
||||
{/* begin::Content */}
|
||||
<div className="px-7 py-5" data-kt-user-table-filter="form">
|
||||
{/* begin::Input group */}
|
||||
<div className="mb-10">
|
||||
<label className="form-label fs-6 fw-bold">Role:</label>
|
||||
<select
|
||||
className="form-select form-select-solid fw-bolder"
|
||||
data-kt-select2="true"
|
||||
data-placeholder="Select option"
|
||||
data-allow-clear="true"
|
||||
data-kt-user-table-filter="role"
|
||||
data-hide-search="true"
|
||||
onChange={(e) => setRole(e.target.value)}
|
||||
value={role}
|
||||
>
|
||||
<option value=""></option>
|
||||
<option value="Administrator">Administrator</option>
|
||||
<option value="Analyst">Analyst</option>
|
||||
<option value="Developer">Developer</option>
|
||||
<option value="Support">Support</option>
|
||||
<option value="Trial">Trial</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="mb-10">
|
||||
<label className="form-label fs-6 fw-bold">Last login:</label>
|
||||
<select
|
||||
className="form-select form-select-solid fw-bolder"
|
||||
data-kt-select2="true"
|
||||
data-placeholder="Select option"
|
||||
data-allow-clear="true"
|
||||
data-kt-user-table-filter="two-step"
|
||||
data-hide-search="true"
|
||||
onChange={(e) => setLastLogin(e.target.value)}
|
||||
value={lastLogin}
|
||||
>
|
||||
<option value=""></option>
|
||||
<option value="Yesterday">Yesterday</option>
|
||||
<option value="20 mins ago">20 mins ago</option>
|
||||
<option value="5 hours ago">5 hours ago</option>
|
||||
<option value="2 days ago">2 days ago</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Actions */}
|
||||
<div className="d-flex justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
onClick={filterData}
|
||||
className="btn btn-light btn-active-light-primary fw-bold me-2 px-6"
|
||||
data-kt-menu-dismiss="true"
|
||||
data-kt-user-table-filter="reset"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
disabled={isLoading}
|
||||
type="button"
|
||||
onClick={resetData}
|
||||
className="btn btn-primary fw-bold px-6"
|
||||
data-kt-menu-dismiss="true"
|
||||
data-kt-user-table-filter="filter"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Actions */}
|
||||
</div>
|
||||
{/* end::Content */}
|
||||
</div>
|
||||
{/* end::SubMenu */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { UsersListFilter };
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useQueryClient, useMutation } from "react-query";
|
||||
import { QUERIES } from "../../../../../../_digifi/helpers";
|
||||
import { useListView } from "../../core/ListViewProvider";
|
||||
import { useQueryResponse } from "../../core/QueryResponseProvider";
|
||||
import { deleteSelectedUsers } from "../../../core/_requests";
|
||||
|
||||
const UsersListGrouping = () => {
|
||||
const { selected, clearSelected } = useListView();
|
||||
const queryClient = useQueryClient();
|
||||
const { query } = useQueryResponse();
|
||||
|
||||
const deleteSelectedItems = useMutation(() => deleteSelectedUsers(selected), {
|
||||
// 💡 response of the mutation is passed to onSuccess
|
||||
onSuccess: () => {
|
||||
// ✅ update detail view directly
|
||||
queryClient.invalidateQueries([`${QUERIES.USERS_LIST}-${query}`]);
|
||||
clearSelected();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="d-flex justify-content-end align-items-center">
|
||||
<div className="fw-bolder me-5">
|
||||
<span className="me-2">{selected.length}</span> Selected
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger"
|
||||
onClick={async () => await deleteSelectedItems.mutateAsync()}
|
||||
>
|
||||
Delete Selected
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { UsersListGrouping };
|
||||
@@ -0,0 +1,22 @@
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {UsersListToolbar} from './UserListToolbar'
|
||||
import {UsersListGrouping} from './UsersListGrouping'
|
||||
import {UsersListSearchComponent} from './UsersListSearchComponent'
|
||||
|
||||
const UsersListHeader = () => {
|
||||
const {selected} = useListView()
|
||||
return (
|
||||
<div className='card-header border-0 pt-6'>
|
||||
<UsersListSearchComponent />
|
||||
{/* begin::Card toolbar */}
|
||||
<div className='card-toolbar'>
|
||||
{/* begin::Group actions */}
|
||||
{selected.length > 0 ? <UsersListGrouping /> : <UsersListToolbar />}
|
||||
{/* end::Group actions */}
|
||||
</div>
|
||||
{/* end::Card toolbar */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {UsersListHeader}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
initialQueryState,
|
||||
KTIcon,
|
||||
useDebounce,
|
||||
} from "../../../../../../_digifi/helpers";
|
||||
import { useQueryRequest } from "../../core/QueryRequestProvider";
|
||||
|
||||
const UsersListSearchComponent = () => {
|
||||
const { updateState } = useQueryRequest();
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
// Debounce search term so that it only gives us latest value ...
|
||||
// ... if searchTerm has not been updated within last 500ms.
|
||||
// The goal is to only have the API call fire when user stops typing ...
|
||||
// ... so that we aren't hitting our API rapidly.
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 150);
|
||||
// Effect for API call
|
||||
useEffect(
|
||||
() => {
|
||||
if (debouncedSearchTerm !== undefined && searchTerm !== undefined) {
|
||||
updateState({ search: debouncedSearchTerm, ...initialQueryState });
|
||||
}
|
||||
},
|
||||
[debouncedSearchTerm] // Only call effect if debounced search term changes
|
||||
// More details about useDebounce: https://usehooks.com/useDebounce/
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="card-title">
|
||||
{/* begin::Search */}
|
||||
<div className="d-flex align-items-center position-relative my-1">
|
||||
<KTIcon iconName="magnifier" className="fs-1 position-absolute ms-6" />
|
||||
<input
|
||||
type="text"
|
||||
data-kt-user-table-filter="search"
|
||||
className="form-control form-control-solid w-250px ps-14"
|
||||
placeholder="Search user"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* end::Search */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { UsersListSearchComponent };
|
||||
@@ -0,0 +1,18 @@
|
||||
const UsersListLoading = () => {
|
||||
const styles = {
|
||||
borderRadius: '0.475rem',
|
||||
boxShadow: '0 0 50px 0 rgb(82 63 105 / 15%)',
|
||||
backgroundColor: '#fff',
|
||||
color: '#7e8299',
|
||||
fontWeight: '500',
|
||||
margin: '0',
|
||||
width: 'auto',
|
||||
padding: '1rem 2rem',
|
||||
top: 'calc(50% - 2rem)',
|
||||
left: 'calc(50% - 4rem)',
|
||||
}
|
||||
|
||||
return <div style={{...styles, position: 'absolute', textAlign: 'center'}}>Processing...</div>
|
||||
}
|
||||
|
||||
export {UsersListLoading}
|
||||
@@ -0,0 +1,179 @@
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
useQueryResponseLoading,
|
||||
useQueryResponsePagination,
|
||||
} from "../../core/QueryResponseProvider";
|
||||
import { useQueryRequest } from "../../core/QueryRequestProvider";
|
||||
import { PaginationState } from "../../../../../../_digifi/helpers";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const mappedLabel = (label: string): string => {
|
||||
if (label === "« Previous") {
|
||||
return "Previous";
|
||||
}
|
||||
|
||||
if (label === "Next »") {
|
||||
return "Next";
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
const UsersListPagination = () => {
|
||||
const pagination = useQueryResponsePagination();
|
||||
const isLoading = useQueryResponseLoading();
|
||||
const { updateState } = useQueryRequest();
|
||||
const updatePage = (page: number | undefined | null) => {
|
||||
if (!page || isLoading || pagination.page === page) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateState({ page, items_per_page: pagination.items_per_page || 10 });
|
||||
};
|
||||
|
||||
const PAGINATION_PAGES_COUNT = 5;
|
||||
const sliceLinks = (pagination?: PaginationState) => {
|
||||
if (!pagination?.links?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const scopedLinks = [...pagination.links];
|
||||
|
||||
let pageLinks: Array<{
|
||||
label: string;
|
||||
active: boolean;
|
||||
url: string | null;
|
||||
page: number | null;
|
||||
}> = [];
|
||||
const previousLink: {
|
||||
label: string;
|
||||
active: boolean;
|
||||
url: string | null;
|
||||
page: number | null;
|
||||
} = scopedLinks.shift()!;
|
||||
const nextLink: {
|
||||
label: string;
|
||||
active: boolean;
|
||||
url: string | null;
|
||||
page: number | null;
|
||||
} = scopedLinks.pop()!;
|
||||
|
||||
const halfOfPagesCount = Math.floor(PAGINATION_PAGES_COUNT / 2);
|
||||
|
||||
pageLinks.push(previousLink);
|
||||
|
||||
if (
|
||||
pagination.page <= Math.round(PAGINATION_PAGES_COUNT / 2) ||
|
||||
scopedLinks.length <= PAGINATION_PAGES_COUNT
|
||||
) {
|
||||
pageLinks = [
|
||||
...pageLinks,
|
||||
...scopedLinks.slice(0, PAGINATION_PAGES_COUNT),
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
pagination.page > scopedLinks.length - halfOfPagesCount &&
|
||||
scopedLinks.length > PAGINATION_PAGES_COUNT
|
||||
) {
|
||||
pageLinks = [
|
||||
...pageLinks,
|
||||
...scopedLinks.slice(
|
||||
scopedLinks.length - PAGINATION_PAGES_COUNT,
|
||||
scopedLinks.length
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
pagination.page <= Math.round(PAGINATION_PAGES_COUNT / 2) ||
|
||||
scopedLinks.length <= PAGINATION_PAGES_COUNT
|
||||
) &&
|
||||
!(pagination.page > scopedLinks.length - halfOfPagesCount)
|
||||
) {
|
||||
pageLinks = [
|
||||
...pageLinks,
|
||||
...scopedLinks.slice(
|
||||
pagination.page - 1 - halfOfPagesCount,
|
||||
pagination.page + halfOfPagesCount
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
pageLinks.push(nextLink);
|
||||
|
||||
return pageLinks;
|
||||
};
|
||||
|
||||
const paginationLinks = useMemo(() => sliceLinks(pagination), [pagination]);
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
|
||||
<div className="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end">
|
||||
<div id="kt_table_users_paginate">
|
||||
<ul className="pagination">
|
||||
<li
|
||||
className={clsx("page-item", {
|
||||
disabled: isLoading || pagination.page === 1,
|
||||
})}
|
||||
>
|
||||
<a
|
||||
onClick={() => updatePage(1)}
|
||||
style={{ cursor: "pointer" }}
|
||||
className="page-link"
|
||||
>
|
||||
First
|
||||
</a>
|
||||
</li>
|
||||
{paginationLinks
|
||||
?.map((link) => {
|
||||
return { ...link, label: mappedLabel(link.label) };
|
||||
})
|
||||
.map((link) => (
|
||||
<li
|
||||
key={link.label}
|
||||
className={clsx("page-item", {
|
||||
active: pagination.page === link.page,
|
||||
disabled: isLoading,
|
||||
previous: link.label === "Previous",
|
||||
next: link.label === "Next",
|
||||
})}
|
||||
>
|
||||
<a
|
||||
className={clsx("page-link", {
|
||||
"page-text":
|
||||
link.label === "Previous" || link.label === "Next",
|
||||
"me-5": link.label === "Previous",
|
||||
})}
|
||||
onClick={() => updatePage(link.page)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
{mappedLabel(link.label)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
className={clsx("page-item", {
|
||||
disabled:
|
||||
isLoading ||
|
||||
pagination.page === (pagination.links?.length || 3) - 2,
|
||||
})}
|
||||
>
|
||||
<a
|
||||
onClick={() => updatePage((pagination.links?.length || 3) - 2)}
|
||||
style={{ cursor: "pointer" }}
|
||||
className="page-link"
|
||||
>
|
||||
Last
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { UsersListPagination };
|
||||
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import { FC, useState, createContext, useContext, useMemo } from "react";
|
||||
import {
|
||||
ID,
|
||||
calculatedGroupingIsDisabled,
|
||||
calculateIsAllDataSelected,
|
||||
groupingOnSelect,
|
||||
initialListView,
|
||||
ListViewContextProps,
|
||||
groupingOnSelectAll,
|
||||
WithChildren,
|
||||
} from "../../../../../_digifi/helpers";
|
||||
import {
|
||||
useQueryResponse,
|
||||
useQueryResponseData,
|
||||
} from "./QueryResponseProvider";
|
||||
|
||||
const ListViewContext = createContext<ListViewContextProps>(initialListView);
|
||||
|
||||
const ListViewProvider: FC<WithChildren> = ({ children }) => {
|
||||
const [selected, setSelected] = useState<Array<ID>>(initialListView.selected);
|
||||
const [itemIdForUpdate, setItemIdForUpdate] = useState<ID>(
|
||||
initialListView.itemIdForUpdate
|
||||
);
|
||||
const { isLoading } = useQueryResponse();
|
||||
const data = useQueryResponseData();
|
||||
const disabled = useMemo(
|
||||
() => calculatedGroupingIsDisabled(isLoading, data),
|
||||
[isLoading, data]
|
||||
);
|
||||
const isAllSelected = useMemo(
|
||||
() => calculateIsAllDataSelected(data, selected),
|
||||
[data, selected]
|
||||
);
|
||||
|
||||
return (
|
||||
<ListViewContext.Provider
|
||||
value={{
|
||||
selected,
|
||||
itemIdForUpdate,
|
||||
setItemIdForUpdate,
|
||||
disabled,
|
||||
isAllSelected,
|
||||
onSelect: (id: ID) => {
|
||||
groupingOnSelect(id, selected, setSelected);
|
||||
},
|
||||
onSelectAll: () => {
|
||||
groupingOnSelectAll(isAllSelected, setSelected, data);
|
||||
},
|
||||
clearSelected: () => {
|
||||
setSelected([]);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ListViewContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useListView = () => useContext(ListViewContext);
|
||||
|
||||
export { ListViewProvider, useListView };
|
||||
@@ -0,0 +1,29 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import { FC, useState, createContext, useContext } from "react";
|
||||
import {
|
||||
QueryState,
|
||||
QueryRequestContextProps,
|
||||
initialQueryRequest,
|
||||
WithChildren,
|
||||
} from "../../../../../_digifi/helpers";
|
||||
|
||||
const QueryRequestContext =
|
||||
createContext<QueryRequestContextProps>(initialQueryRequest);
|
||||
|
||||
const QueryRequestProvider: FC<WithChildren> = ({ children }) => {
|
||||
const [state, setState] = useState<QueryState>(initialQueryRequest.state);
|
||||
|
||||
const updateState = (updates: Partial<QueryState>) => {
|
||||
const updatedState = { ...state, ...updates } as QueryState;
|
||||
setState(updatedState);
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryRequestContext.Provider value={{ state, updateState }}>
|
||||
{children}
|
||||
</QueryRequestContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useQueryRequest = () => useContext(QueryRequestContext);
|
||||
export { QueryRequestProvider, useQueryRequest };
|
||||
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { FC, useContext, useState, useEffect, useMemo } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import {
|
||||
createResponseContext,
|
||||
initialQueryResponse,
|
||||
initialQueryState,
|
||||
PaginationState,
|
||||
QUERIES,
|
||||
stringifyRequestQuery,
|
||||
WithChildren,
|
||||
} from "../../../../../_digifi/helpers";
|
||||
import { getVerifiedUsers } from "../../core/_requests";
|
||||
import { User } from "../../core/_models";
|
||||
import { useQueryRequest } from "./QueryRequestProvider";
|
||||
|
||||
const QueryResponseContext = createResponseContext<User>(initialQueryResponse);
|
||||
const QueryResponseProvider: FC<WithChildren> = ({ children }) => {
|
||||
const { state } = useQueryRequest();
|
||||
const [query, setQuery] = useState<string>(stringifyRequestQuery(state));
|
||||
const updatedQuery = useMemo(() => stringifyRequestQuery(state), [state]);
|
||||
|
||||
useEffect(() => {
|
||||
if (query !== updatedQuery) {
|
||||
setQuery(updatedQuery);
|
||||
}
|
||||
}, [updatedQuery]);
|
||||
|
||||
const {
|
||||
isFetching,
|
||||
refetch,
|
||||
data: response,
|
||||
} = useQuery(
|
||||
`${QUERIES.VERIFIED_LIST}-${query}`,
|
||||
() => {
|
||||
return getVerifiedUsers(query);
|
||||
},
|
||||
{ cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryResponseContext.Provider
|
||||
value={{ isLoading: isFetching, refetch, response, query }}
|
||||
>
|
||||
{children}
|
||||
</QueryResponseContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useQueryResponse = () => useContext(QueryResponseContext);
|
||||
|
||||
const useQueryResponseData = () => {
|
||||
const { response } = useQueryResponse();
|
||||
if (!response) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response?.records || [];
|
||||
};
|
||||
|
||||
const useQueryResponsePagination = () => {
|
||||
const defaultPaginationState: PaginationState = {
|
||||
links: [],
|
||||
...initialQueryState,
|
||||
};
|
||||
|
||||
const { response } = useQueryResponse();
|
||||
if (!response || !response.payload || !response.payload.pagination) {
|
||||
return defaultPaginationState;
|
||||
}
|
||||
|
||||
return response.payload.pagination;
|
||||
};
|
||||
|
||||
const useQueryResponseLoading = (): boolean => {
|
||||
const { isLoading } = useQueryResponse();
|
||||
return isLoading;
|
||||
};
|
||||
|
||||
export {
|
||||
QueryResponseProvider,
|
||||
useQueryResponse,
|
||||
useQueryResponseData,
|
||||
useQueryResponsePagination,
|
||||
useQueryResponseLoading,
|
||||
};
|
||||
+2
-4
@@ -2,9 +2,7 @@ import {useEffect} from 'react'
|
||||
import {UserEditModalHeader} from './UserEditModalHeader'
|
||||
import {UserEditModalFormWrapper} from './UserEditModalFormWrapper'
|
||||
|
||||
|
||||
const UserEditModal = () => {
|
||||
|
||||
const EditLoanModal = () => {
|
||||
useEffect(() => {
|
||||
document.body.classList.add('modal-open')
|
||||
return () => {
|
||||
@@ -43,4 +41,4 @@ const UserEditModal = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export {UserEditModal}
|
||||
export {EditLoanModal}
|
||||
@@ -0,0 +1,434 @@
|
||||
import { FC, useState } from "react";
|
||||
import * as Yup from "yup";
|
||||
import { useFormik } from "formik";
|
||||
import { isNotEmpty, toAbsoluteUrl } from "../../../../../_digifi/helpers";
|
||||
import { initialUser, User } from "../../core/_models";
|
||||
import clsx from "clsx";
|
||||
import { useListView } from "../core/ListViewProvider";
|
||||
import { UsersListLoading } from "../components/loading/UsersListLoading";
|
||||
import { createUser, updateUser } from "../../core/_requests";
|
||||
import { useQueryResponse } from "../core/QueryResponseProvider";
|
||||
|
||||
type Props = {
|
||||
isUserLoading: boolean;
|
||||
user: User;
|
||||
};
|
||||
|
||||
const editUserSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email("Wrong email format")
|
||||
.min(3, "Minimum 3 symbols")
|
||||
.max(50, "Maximum 50 symbols")
|
||||
.required("Email is required"),
|
||||
name: Yup.string()
|
||||
.min(3, "Minimum 3 symbols")
|
||||
.max(50, "Maximum 50 symbols")
|
||||
.required("Name is required"),
|
||||
});
|
||||
|
||||
const UserEditModalForm: FC<Props> = ({ user, isUserLoading }) => {
|
||||
const { setItemIdForUpdate } = useListView();
|
||||
const { refetch } = useQueryResponse();
|
||||
|
||||
const [userForEdit] = useState<User>({
|
||||
...user,
|
||||
avatar: user.avatar || initialUser.avatar,
|
||||
role: user.role || initialUser.role,
|
||||
position: user.position || initialUser.position,
|
||||
name: user.name || initialUser.name,
|
||||
email: user.email || initialUser.email,
|
||||
});
|
||||
|
||||
const cancel = (withRefresh?: boolean) => {
|
||||
if (withRefresh) {
|
||||
refetch();
|
||||
}
|
||||
setItemIdForUpdate(undefined);
|
||||
};
|
||||
|
||||
const blankImg = toAbsoluteUrl("media/svg/avatars/blank.svg");
|
||||
const userAvatarImg = toAbsoluteUrl(`media/${userForEdit.avatar}`);
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: userForEdit,
|
||||
validationSchema: editUserSchema,
|
||||
onSubmit: async (values, { setSubmitting }) => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (isNotEmpty(values.id)) {
|
||||
await updateUser(values);
|
||||
} else {
|
||||
await createUser(values);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
} finally {
|
||||
setSubmitting(true);
|
||||
cancel(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
id="kt_modal_add_user_form"
|
||||
className="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
>
|
||||
{/* begin::Scroll */}
|
||||
<div
|
||||
// className="d-flex flex-column scroll-y me-n7 pe-7"
|
||||
className="d-none flex-column scroll-y me-n7 pe-7"
|
||||
id="kt_modal_add_user_scroll"
|
||||
data-kt-scroll="true"
|
||||
data-kt-scroll-activate="{default: false, lg: true}"
|
||||
data-kt-scroll-max-height="auto"
|
||||
data-kt-scroll-dependencies="#kt_modal_add_user_header"
|
||||
data-kt-scroll-wrappers="#kt_modal_add_user_scroll"
|
||||
data-kt-scroll-offset="300px"
|
||||
>
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="d-block fw-bold fs-6 mb-5">Avatar</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Image input */}
|
||||
<div
|
||||
className="image-input image-input-outline"
|
||||
data-kt-image-input="true"
|
||||
style={{ backgroundImage: `url('${blankImg}')` }}
|
||||
>
|
||||
{/* begin::Preview existing avatar */}
|
||||
<div
|
||||
className="image-input-wrapper w-125px h-125px"
|
||||
style={{ backgroundImage: `url('${userAvatarImg}')` }}
|
||||
></div>
|
||||
{/* end::Preview existing avatar */}
|
||||
|
||||
{/* begin::Label */}
|
||||
{/* <label
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='change'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Change avatar'
|
||||
>
|
||||
<i className='bi bi-pencil-fill fs-7'></i>
|
||||
|
||||
<input type='file' name='avatar' accept='.png, .jpg, .jpeg' />
|
||||
<input type='hidden' name='avatar_remove' />
|
||||
</label> */}
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Cancel */}
|
||||
{/* <span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='cancel'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Cancel avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span> */}
|
||||
{/* end::Cancel */}
|
||||
|
||||
{/* begin::Remove */}
|
||||
{/* <span
|
||||
className='btn btn-icon btn-circle btn-active-color-primary w-25px h-25px bg-body shadow'
|
||||
data-kt-image-input-action='remove'
|
||||
data-bs-toggle='tooltip'
|
||||
title='Remove avatar'
|
||||
>
|
||||
<i className='bi bi-x fs-2'></i>
|
||||
</span> */}
|
||||
{/* end::Remove */}
|
||||
</div>
|
||||
{/* end::Image input */}
|
||||
|
||||
{/* begin::Hint */}
|
||||
{/* <div className='form-text'>Allowed file types: png, jpg, jpeg.</div> */}
|
||||
{/* end::Hint */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Full Name</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Full name"
|
||||
{...formik.getFieldProps("name")}
|
||||
type="text"
|
||||
name="name"
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.name && formik.errors.name },
|
||||
{
|
||||
"is-valid": formik.touched.name && !formik.errors.name,
|
||||
}
|
||||
)}
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<div className="fv-help-block">
|
||||
<span role="alert">{formik.errors.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* end::Input */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="fv-row mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-2">Email</label>
|
||||
{/* end::Label */}
|
||||
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
placeholder="Email"
|
||||
{...formik.getFieldProps("email")}
|
||||
className={clsx(
|
||||
"form-control form-control-solid mb-3 mb-lg-0",
|
||||
{ "is-invalid": formik.touched.email && formik.errors.email },
|
||||
{
|
||||
"is-valid": formik.touched.email && !formik.errors.email,
|
||||
}
|
||||
)}
|
||||
type="email"
|
||||
name="email"
|
||||
autoComplete="off"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<div className="fv-plugins-message-container">
|
||||
<span role="alert">{formik.errors.email}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
|
||||
{/* begin::Input group */}
|
||||
<div className="mb-7">
|
||||
{/* begin::Label */}
|
||||
<label className="required fw-bold fs-6 mb-5">Role</label>
|
||||
{/* end::Label */}
|
||||
{/* begin::Roles */}
|
||||
{/* begin::Input row */}
|
||||
<div className="d-flex fv-row">
|
||||
{/* begin::Radio */}
|
||||
<div className="form-check form-check-custom form-check-solid">
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className="form-check-input me-3"
|
||||
{...formik.getFieldProps("role")}
|
||||
name="role"
|
||||
type="radio"
|
||||
value="Administrator"
|
||||
id="kt_modal_update_role_option_0"
|
||||
checked={formik.values.role === "Administrator"}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="kt_modal_update_role_option_0"
|
||||
>
|
||||
<div className="fw-bolder text-gray-800">Administrator</div>
|
||||
<div className="text-gray-600">
|
||||
Best for business owners and company administrators
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className="separator separator-dashed my-5"></div>
|
||||
{/* begin::Input row */}
|
||||
<div className="d-flex fv-row">
|
||||
{/* begin::Radio */}
|
||||
<div className="form-check form-check-custom form-check-solid">
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className="form-check-input me-3"
|
||||
{...formik.getFieldProps("role")}
|
||||
name="role"
|
||||
type="radio"
|
||||
value="Developer"
|
||||
id="kt_modal_update_role_option_1"
|
||||
checked={formik.values.role === "Developer"}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="kt_modal_update_role_option_1"
|
||||
>
|
||||
<div className="fw-bolder text-gray-800">Developer</div>
|
||||
<div className="text-gray-600">
|
||||
Best for developers or people primarily using the API
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className="separator separator-dashed my-5"></div>
|
||||
{/* begin::Input row */}
|
||||
<div className="d-flex fv-row">
|
||||
{/* begin::Radio */}
|
||||
<div className="form-check form-check-custom form-check-solid">
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className="form-check-input me-3"
|
||||
{...formik.getFieldProps("role")}
|
||||
name="role"
|
||||
type="radio"
|
||||
value="Analyst"
|
||||
id="kt_modal_update_role_option_2"
|
||||
checked={formik.values.role === "Analyst"}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="kt_modal_update_role_option_2"
|
||||
>
|
||||
<div className="fw-bolder text-gray-800">Analyst</div>
|
||||
<div className="text-gray-600">
|
||||
Best for people who need full access to analytics data, but
|
||||
don't need to update business settings
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className="separator separator-dashed my-5"></div>
|
||||
{/* begin::Input row */}
|
||||
<div className="d-flex fv-row">
|
||||
{/* begin::Radio */}
|
||||
<div className="form-check form-check-custom form-check-solid">
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className="form-check-input me-3"
|
||||
{...formik.getFieldProps("role")}
|
||||
name="role"
|
||||
type="radio"
|
||||
value="Support"
|
||||
id="kt_modal_update_role_option_3"
|
||||
checked={formik.values.role === "Support"}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="kt_modal_update_role_option_3"
|
||||
>
|
||||
<div className="fw-bolder text-gray-800">Support</div>
|
||||
<div className="text-gray-600">
|
||||
Best for employees who regularly refund payments and respond
|
||||
to disputes
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
<div className="separator separator-dashed my-5"></div>
|
||||
{/* begin::Input row */}
|
||||
<div className="d-flex fv-row">
|
||||
{/* begin::Radio */}
|
||||
<div className="form-check form-check-custom form-check-solid">
|
||||
{/* begin::Input */}
|
||||
<input
|
||||
className="form-check-input me-3"
|
||||
{...formik.getFieldProps("role")}
|
||||
name="role"
|
||||
type="radio"
|
||||
id="kt_modal_update_role_option_4"
|
||||
value="Trial"
|
||||
checked={formik.values.role === "Trial"}
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
/>
|
||||
{/* end::Input */}
|
||||
{/* begin::Label */}
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="kt_modal_update_role_option_4"
|
||||
>
|
||||
<div className="fw-bolder text-gray-800">Trial</div>
|
||||
<div className="text-gray-600">
|
||||
Best for people who need to preview content data, but don't
|
||||
need to make any updates
|
||||
</div>
|
||||
</label>
|
||||
{/* end::Label */}
|
||||
</div>
|
||||
{/* end::Radio */}
|
||||
</div>
|
||||
{/* end::Input row */}
|
||||
{/* end::Roles */}
|
||||
</div>
|
||||
{/* end::Input group */}
|
||||
</div>
|
||||
{/* end::Scroll */}
|
||||
|
||||
{/* begin::Actions */}
|
||||
<div className="text-center pt-15">
|
||||
<button
|
||||
type="reset"
|
||||
onClick={() => cancel()}
|
||||
className="btn btn-danger me-3"
|
||||
data-kt-users-modal-action="cancel"
|
||||
disabled={formik.isSubmitting || isUserLoading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
data-kt-users-modal-action="submit"
|
||||
disabled={
|
||||
isUserLoading ||
|
||||
formik.isSubmitting ||
|
||||
!formik.isValid ||
|
||||
!formik.touched
|
||||
}
|
||||
>
|
||||
<span className="indicator-label">Submit</span>
|
||||
{(formik.isSubmitting || isUserLoading) && (
|
||||
<span className="indicator-progress">
|
||||
Please wait...{" "}
|
||||
<span className="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{/* end::Actions */}
|
||||
</form>
|
||||
{(formik.isSubmitting || isUserLoading) && <UsersListLoading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { UserEditModalForm };
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { UserEditModalForm } from "./UserEditModalForm";
|
||||
import { isNotEmpty, QUERIES } from "../../../../../_digifi/helpers";
|
||||
import { useListView } from "../core/ListViewProvider";
|
||||
import { getUserById, getApprovedUsers } from "../../core/_requests";
|
||||
|
||||
const UserEditModalFormWrapper = () => {
|
||||
const { itemIdForUpdate, setItemIdForUpdate } = useListView();
|
||||
const enabledQuery: boolean = isNotEmpty(itemIdForUpdate);
|
||||
const {
|
||||
isLoading,
|
||||
data: user,
|
||||
error,
|
||||
} = useQuery(
|
||||
`${QUERIES.READY_LIST}-user-${itemIdForUpdate}`,
|
||||
() => {
|
||||
// return getUserById(itemIdForUpdate);
|
||||
return getApprovedUsers('');
|
||||
},
|
||||
{
|
||||
cacheTime: 0,
|
||||
enabled: enabledQuery,
|
||||
onError: (err) => {
|
||||
setItemIdForUpdate(undefined);
|
||||
console.error(err);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!itemIdForUpdate) {
|
||||
return (
|
||||
<UserEditModalForm isUserLoading={isLoading} user={{ id: undefined }} />
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && !error && user) {
|
||||
// return <UserEditModalForm isUserLoading={isLoading} user={user} />;
|
||||
// REMOVE LATER AND ALLOW UP ALONE
|
||||
let newUser:any = user?.records
|
||||
return <UserEditModalForm isUserLoading={isLoading} user={newUser} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export { UserEditModalFormWrapper };
|
||||
@@ -0,0 +1,27 @@
|
||||
import { KTIcon } from "../../../../../_digifi/helpers";
|
||||
import { useListView } from "../core/ListViewProvider";
|
||||
|
||||
const UserEditModalHeader = () => {
|
||||
const { setItemIdForUpdate } = useListView();
|
||||
|
||||
return (
|
||||
<div className="modal-header">
|
||||
{/* begin::Modal title */}
|
||||
<h2 className="fw-bolder">Edit Loan</h2>
|
||||
{/* end::Modal title */}
|
||||
|
||||
{/* begin::Close */}
|
||||
<div
|
||||
className="btn btn-icon btn-sm btn-active-icon-primary"
|
||||
data-kt-users-modal-action="close"
|
||||
onClick={() => setItemIdForUpdate(undefined)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<KTIcon iconName="cross" className="fs-1" />
|
||||
</div>
|
||||
{/* end::Close */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { UserEditModalHeader };
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useMemo } from "react";
|
||||
import { useTable, ColumnInstance, Row } from "react-table";
|
||||
import { CustomHeaderColumn } from "./columns/CustomHeaderColumn";
|
||||
import { CustomRow } from "./columns/CustomRow";
|
||||
import {
|
||||
useQueryResponseData,
|
||||
useQueryResponseLoading,
|
||||
} from "../core/QueryResponseProvider";
|
||||
import { usersColumns } from "./columns/_columns";
|
||||
import { User } from "../../core/_models";
|
||||
import { UsersListLoading } from "../components/loading/UsersListLoading";
|
||||
import { UsersListPagination } from "../components/pagination/UsersListPagination";
|
||||
import { KTCardBody } from "../../../../../_digifi/helpers";
|
||||
|
||||
const UsersTable = () => {
|
||||
const users = useQueryResponseData();
|
||||
// console.log('users44', users)
|
||||
const isLoading = useQueryResponseLoading();
|
||||
const data = useMemo(() => users, [users]);
|
||||
const columns = useMemo(() => usersColumns, []);
|
||||
const { getTableProps, getTableBodyProps, headers, rows, prepareRow } =
|
||||
useTable({
|
||||
columns,
|
||||
data,
|
||||
});
|
||||
|
||||
return (
|
||||
<KTCardBody className="py-4">
|
||||
<div className="table-responsive">
|
||||
<table
|
||||
id="kt_table_users"
|
||||
className="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer"
|
||||
{...getTableProps()}
|
||||
>
|
||||
<thead>
|
||||
<tr className="text-start text-muted fw-bolder fs-7 text-uppercase gs-0">
|
||||
{headers.map((column: ColumnInstance<User>) => (
|
||||
<CustomHeaderColumn key={column.id} column={column} />
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-gray-600 fw-bold" {...getTableBodyProps()}>
|
||||
{rows.length > 0 ? (
|
||||
rows.map((row: Row<User>, i) => {
|
||||
prepareRow(row);
|
||||
return <CustomRow row={row} key={`row-${i}-${row.id}`} />;
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={7}>
|
||||
<div className="d-flex text-center w-100 align-content-center justify-content-center">
|
||||
No matching records found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<UsersListPagination />
|
||||
{isLoading && <UsersListLoading />}
|
||||
</KTCardBody>
|
||||
);
|
||||
};
|
||||
|
||||
export { UsersTable };
|
||||
@@ -0,0 +1,14 @@
|
||||
import { FC } from "react";
|
||||
import { NewDateTimeFormatter } from "../../../../../../_digifi/lib/NewDateTimeFormatter";
|
||||
|
||||
type Props = {
|
||||
added?: string;
|
||||
};
|
||||
|
||||
const AddedCell: FC<Props> = ({ added }) => (
|
||||
<div className="badge badge-light fw-bolder">
|
||||
{NewDateTimeFormatter(added)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export { AddedCell };
|
||||
@@ -0,0 +1,11 @@
|
||||
import {FC} from 'react'
|
||||
|
||||
type Props = {
|
||||
agent?: string
|
||||
}
|
||||
|
||||
const AgentCell: FC<Props> = ({agent}) => (
|
||||
<> {agent && <div className='badge badge-light-success fw-bolder'>{agent}</div>}</>
|
||||
)
|
||||
|
||||
export {AgentCell}
|
||||
@@ -0,0 +1,15 @@
|
||||
import {FC} from 'react'
|
||||
import {ColumnInstance} from 'react-table'
|
||||
import {User} from '../../../core/_models'
|
||||
|
||||
type Props = {
|
||||
column: ColumnInstance<User>
|
||||
}
|
||||
|
||||
const CustomHeaderColumn: FC<Props> = ({column}) => (
|
||||
<>
|
||||
{column.Header && typeof column.Header === 'string' ? <th {...column.getHeaderProps()}>{column.render('Header')}</th> : column.render('Header')}
|
||||
</>
|
||||
)
|
||||
|
||||
export {CustomHeaderColumn}
|
||||
@@ -0,0 +1,25 @@
|
||||
import clsx from 'clsx'
|
||||
import {FC} from 'react'
|
||||
import {Row} from 'react-table'
|
||||
import {User} from '../../../core/_models'
|
||||
|
||||
type Props = {
|
||||
row: Row<User>
|
||||
}
|
||||
|
||||
const CustomRow: FC<Props> = ({row}) => (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell) => {
|
||||
return (
|
||||
<td
|
||||
{...cell.getCellProps()}
|
||||
className={clsx({'text-end min-w-100px': cell.column.id === 'actions'})}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
|
||||
export {CustomRow}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {FC} from 'react'
|
||||
|
||||
type Props = {
|
||||
employer_name?: string
|
||||
}
|
||||
|
||||
const EmployerCell: FC<Props> = ({employer_name}) => (
|
||||
<div className='badge badge-light fw-bolder'>{employer_name}</div>
|
||||
)
|
||||
|
||||
export {EmployerCell}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { FC, useEffect } from "react";
|
||||
import { useQueryClient } from "react-query";
|
||||
import { MenuComponent } from "../../../../../../_digifi/assets/ts/components";
|
||||
import { ID, KTIcon, QUERIES } from "../../../../../../_digifi/helpers";
|
||||
import { useListView } from "../../core/ListViewProvider";
|
||||
import { useQueryResponse } from "../../core/QueryResponseProvider";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { User } from "../../../core/_models";
|
||||
|
||||
type Props = {
|
||||
id: ID;
|
||||
data: Array<User> | any
|
||||
};
|
||||
|
||||
const UserActionsCell: FC<Props> = ({ id, data }) => {
|
||||
// const { setItemIdForUpdate } = useListView();
|
||||
// const { query } = useQueryResponse();
|
||||
// const queryClient = useQueryClient();
|
||||
|
||||
let selectedUser = data?.filter((item:User) => item.uid == id)[0]
|
||||
|
||||
useEffect(() => {
|
||||
MenuComponent.reinitialization();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
href="#"
|
||||
className="btn btn-light btn-active-light-primary btn-sm"
|
||||
data-kt-menu-trigger="click"
|
||||
data-kt-menu-placement="bottom-end"
|
||||
>
|
||||
Actions
|
||||
<KTIcon iconName="down" className="fs-5 m-0" />
|
||||
</a>
|
||||
{/* begin::Menu */}
|
||||
<div
|
||||
className="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold fs-7 w-175px py-4"
|
||||
data-kt-menu="true"
|
||||
>
|
||||
{/* begin::Menu item */}
|
||||
<div className="menu-item px-3">
|
||||
<Link state={{selectedUser}} to='/loan/verified/process' className="menu-link px-3">
|
||||
Process
|
||||
</Link>
|
||||
</div>
|
||||
{/* end::Menu item */}
|
||||
</div>
|
||||
{/* end::Menu */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { UserActionsCell };
|
||||
@@ -0,0 +1,61 @@
|
||||
import clsx from "clsx";
|
||||
import { FC, PropsWithChildren, useMemo } from "react";
|
||||
import { HeaderProps } from "react-table";
|
||||
import { initialQueryState } from "../../../../../../_digifi/helpers";
|
||||
import { useQueryRequest } from "../../core/QueryRequestProvider";
|
||||
import { User } from "../../../core/_models";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
title?: string;
|
||||
tableProps: PropsWithChildren<HeaderProps<User>>;
|
||||
};
|
||||
const UserCustomHeader: FC<Props> = ({ className, title, tableProps }) => {
|
||||
const id = tableProps.column.id;
|
||||
const { state, updateState } = useQueryRequest();
|
||||
|
||||
const isSelectedForSorting = useMemo(() => {
|
||||
return state.sort && state.sort === id;
|
||||
}, [state, id]);
|
||||
const order: "asc" | "desc" | undefined = useMemo(() => state.order, [state]);
|
||||
|
||||
const sortColumn = () => {
|
||||
// avoid sorting for these columns
|
||||
if (id === "actions" || id === "selection") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSelectedForSorting) {
|
||||
// enable sort asc
|
||||
updateState({ sort: id, order: "asc", ...initialQueryState });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelectedForSorting && order !== undefined) {
|
||||
if (order === "asc") {
|
||||
// enable sort desc
|
||||
updateState({ sort: id, order: "desc", ...initialQueryState });
|
||||
return;
|
||||
}
|
||||
|
||||
// disable sort
|
||||
updateState({ sort: undefined, order: undefined, ...initialQueryState });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<th
|
||||
{...tableProps.column.getHeaderProps()}
|
||||
className={clsx(
|
||||
className,
|
||||
isSelectedForSorting && order !== undefined && `table-sort-${order}`
|
||||
)}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={sortColumn}
|
||||
>
|
||||
{title}
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
export { UserCustomHeader };
|
||||
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import { FC } from "react";
|
||||
import { toAbsoluteUrl } from "../../../../../../_digifi/helpers";
|
||||
import { User } from "../../../core/_models";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
const UserInfoCell: FC<Props> = ({ user }) => (
|
||||
<div className="d-flex align-items-center">
|
||||
{/* begin:: Avatar */}
|
||||
<div className="symbol symbol-circle symbol-50px overflow-hidden me-3">
|
||||
<a href="#">
|
||||
{user.avatar ? (
|
||||
<div className="symbol-label">
|
||||
<img
|
||||
src={toAbsoluteUrl(`media/${user.avatar}`)}
|
||||
alt={user.name}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
"symbol-label fs-3",
|
||||
`bg-light-${user.initials?.state}`,
|
||||
`text-${user.initials?.state}`
|
||||
)}
|
||||
>
|
||||
{user.firstname?.substring(0, 1).toUpperCase()}{" "}
|
||||
{user.lastname?.substring(0, 1).toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
<div className="d-flex flex-column">
|
||||
<a href="#" className="text-gray-800 text-hover-primary mb-1">
|
||||
{user.firstname} {user.lastname}
|
||||
</a>
|
||||
<span>{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { UserInfoCell };
|
||||
@@ -0,0 +1,11 @@
|
||||
import {FC} from 'react'
|
||||
|
||||
type Props = {
|
||||
payment_month?: string
|
||||
}
|
||||
|
||||
const PaymentMonthCell: FC<Props> = ({payment_month}) => (
|
||||
<div className='badge badge-light fw-bolder'>{payment_month}</div>
|
||||
)
|
||||
|
||||
export {PaymentMonthCell}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { FC, useMemo } from "react";
|
||||
import { ID } from "../../../../../../_digifi/helpers";
|
||||
import { useListView } from "../../core/ListViewProvider";
|
||||
|
||||
type Props = {
|
||||
id: ID;
|
||||
};
|
||||
|
||||
const UserSelectionCell: FC<Props> = ({ id }) => {
|
||||
const { selected, onSelect } = useListView();
|
||||
const isSelected = useMemo(() => selected.includes(id), [id, selected]);
|
||||
return (
|
||||
<div className="form-check form-check-custom form-check-solid">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
data-kt-check={isSelected}
|
||||
data-kt-check-target="#kt_table_users .form-check-input"
|
||||
checked={isSelected}
|
||||
onChange={() => onSelect(id)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { UserSelectionCell };
|
||||
@@ -0,0 +1,28 @@
|
||||
import {FC, PropsWithChildren} from 'react'
|
||||
import {HeaderProps} from 'react-table'
|
||||
import {useListView} from '../../core/ListViewProvider'
|
||||
import {User} from '../../../core/_models'
|
||||
|
||||
type Props = {
|
||||
tableProps: PropsWithChildren<HeaderProps<User>>
|
||||
}
|
||||
|
||||
const UserSelectionHeader: FC<Props> = ({tableProps}) => {
|
||||
const {isAllSelected, onSelectAll} = useListView()
|
||||
return (
|
||||
<th {...tableProps.column.getHeaderProps()} className='w-10px pe-2'>
|
||||
<div className='form-check form-check-sm form-check-custom form-check-solid me-3'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
data-kt-check={isAllSelected}
|
||||
data-kt-check-target='#kt_table_users .form-check-input'
|
||||
checked={isAllSelected}
|
||||
onChange={onSelectAll}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
}
|
||||
|
||||
export {UserSelectionHeader}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {Column} from 'react-table'
|
||||
import {UserInfoCell} from './UserInfoCell'
|
||||
import { PaymentMonthCell } from './UserLastLoginCell'
|
||||
import {AgentCell} from './AgentCell'
|
||||
import {UserActionsCell} from './UserActionsCell'
|
||||
import {UserSelectionCell} from './UserSelectionCell'
|
||||
import {UserCustomHeader} from './UserCustomHeader'
|
||||
import {UserSelectionHeader} from './UserSelectionHeader'
|
||||
import {User} from '../../../core/_models'
|
||||
import { AddedCell } from './AddedCell'
|
||||
import { EmployerCell } from './EmployerCell'
|
||||
|
||||
const usersColumns: ReadonlyArray<Column<User>> = [
|
||||
{
|
||||
Header: (props) => <UserSelectionHeader tableProps={props} />,
|
||||
id: 'selection',
|
||||
Cell: ({...props}) => <UserSelectionCell id={props.data[props.row.index].uid} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => <UserCustomHeader tableProps={props} title='Name' className='min-w-125px' />,
|
||||
id: 'firstname',
|
||||
Cell: ({...props}) => <UserInfoCell user={props.data[props.row.index]} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Employer' className='min-w-125px' />
|
||||
),
|
||||
id: 'employer_name',
|
||||
Cell: ({...props}) => <EmployerCell employer_name={props.data[props.row.index].employer_name} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => <UserCustomHeader tableProps={props} title='Amount' className='min-w-125px' />,
|
||||
accessor: 'loan_amount',
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Payment Terms' className='min-w-125px' />
|
||||
),
|
||||
id: 'payment_month',
|
||||
Cell: ({...props}) => <PaymentMonthCell payment_month={props.data[props.row.index].payment_month} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Agent' className='min-w-125px' />
|
||||
),
|
||||
id: 'sales_agent',
|
||||
Cell: ({...props}) => <AgentCell agent={props.data[props.row.index].sales_agent} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Added' className='min-w-125px' />
|
||||
),
|
||||
id: 'added',
|
||||
Cell: ({...props}) => <AddedCell added={props.data[props.row.index].added} />,
|
||||
},
|
||||
{
|
||||
Header: (props) => (
|
||||
<UserCustomHeader tableProps={props} title='Actions' className='text-end min-w-100px' />
|
||||
),
|
||||
id: 'actions',
|
||||
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].uid} data={props.data} />,
|
||||
},
|
||||
]
|
||||
|
||||
export {usersColumns}
|
||||
@@ -1,11 +1,11 @@
|
||||
import {lazy, FC, Suspense} from 'react'
|
||||
import {Route, Routes, Navigate} from 'react-router-dom'
|
||||
import {MasterLayout} from '../../_digifi/layout/MasterLayout'
|
||||
import TopBarProgress from 'react-topbar-progress-indicator'
|
||||
import {DashboardWrapper} from '../pages/dashboard/DashboardWrapper'
|
||||
import { lazy, FC, Suspense } from "react";
|
||||
import { Route, Routes, Navigate } from "react-router-dom";
|
||||
import { MasterLayout } from "../../_digifi/layout/MasterLayout";
|
||||
import TopBarProgress from "react-topbar-progress-indicator";
|
||||
import { DashboardWrapper } from "../pages/dashboard/DashboardWrapper";
|
||||
// import {MenuTestPage} from '../pages/MenuTestPage'
|
||||
import {getCSSVariableValue} from '../../_digifi/assets/ts/_utils'
|
||||
import {WithChildren} from '../../_digifi/helpers'
|
||||
import { getCSSVariableValue } from "../../_digifi/assets/ts/_utils";
|
||||
import { WithChildren } from "../../_digifi/helpers";
|
||||
// import BuilderPageWrapper from '../pages/layout-builder/BuilderPageWrapper'
|
||||
|
||||
const PrivateRoutes = () => {
|
||||
@@ -13,28 +13,43 @@ const PrivateRoutes = () => {
|
||||
// const AccountPage = lazy(() => import('../modules/accounts/AccountPage'))
|
||||
// const WidgetsPage = lazy(() => import('../modules/widgets/WidgetsPage'))
|
||||
// const ChatPage = lazy(() => import('../modules/apps/chat/ChatPage'))
|
||||
const ProcessPage = lazy(() => import('../modules/process/ProcessPage'))
|
||||
const UsersPage = lazy(() => import('../modules/apps/user-management/UsersPage'))
|
||||
const EmployersPage =lazy(() => import('../modules/employers/employers-list/UsersPage'))
|
||||
const ProcessPage = lazy(() => import("../modules/process/ProcessPage"));
|
||||
const UsersPage = lazy(
|
||||
() => import("../modules/apps/user-management/UsersPage")
|
||||
);
|
||||
const EmployersPage = lazy(
|
||||
() => import("../modules/employers/employers-list/UsersPage")
|
||||
);
|
||||
const ApproveRejectRoutes = lazy(
|
||||
() => import("../modules/process/approve-reject-page/ApproveRejectRoutes")
|
||||
);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<MasterLayout />}>
|
||||
{/* Redirect to Dashboard after success login/registartion */}
|
||||
<Route path='auth/*' element={<Navigate to='/dashboard' />} />
|
||||
<Route path="auth/*" element={<Navigate to="/dashboard" />} />
|
||||
{/* Pages */}
|
||||
<Route path='dashboard' element={<DashboardWrapper />} />
|
||||
<Route path="dashboard" element={<DashboardWrapper />} />
|
||||
{/* <Route path='builder' element={<BuilderPageWrapper />} /> */}
|
||||
{/* <Route path='menu-test' element={<MenuTestPage />} /> */}
|
||||
{/* Lazy Modules */}
|
||||
<Route
|
||||
path='loan/pages/process/*'
|
||||
path="loan/pages/process/*"
|
||||
element={
|
||||
<SuspensedView>
|
||||
<ProcessPage />
|
||||
</SuspensedView>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="loan/verified/*"
|
||||
element={
|
||||
<SuspensedView>
|
||||
<ApproveRejectRoutes />
|
||||
</SuspensedView>
|
||||
}
|
||||
/>
|
||||
{/* <Route
|
||||
path='crafted/pages/wizards/*'
|
||||
element={
|
||||
@@ -68,7 +83,7 @@ const PrivateRoutes = () => {
|
||||
}
|
||||
/> */}
|
||||
<Route
|
||||
path='tools/user-management/*'
|
||||
path="tools/user-management/*"
|
||||
element={
|
||||
<SuspensedView>
|
||||
<UsersPage />
|
||||
@@ -76,7 +91,7 @@ const PrivateRoutes = () => {
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/employers/*'
|
||||
path="/employers/*"
|
||||
element={
|
||||
<SuspensedView>
|
||||
<EmployersPage />
|
||||
@@ -84,22 +99,22 @@ const PrivateRoutes = () => {
|
||||
}
|
||||
/>
|
||||
{/* Page Not Found */}
|
||||
<Route path='*' element={<Navigate to='/error/404' />} />
|
||||
<Route path="*" element={<Navigate to="/error/404" />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const SuspensedView: FC<WithChildren> = ({children}) => {
|
||||
const baseColor = getCSSVariableValue('--bs-primary')
|
||||
const SuspensedView: FC<WithChildren> = ({ children }) => {
|
||||
const baseColor = getCSSVariableValue("--bs-primary");
|
||||
TopBarProgress.config({
|
||||
barColors: {
|
||||
'0': baseColor,
|
||||
"0": baseColor,
|
||||
},
|
||||
barThickness: 1,
|
||||
shadowBlur: 5,
|
||||
})
|
||||
return <Suspense fallback={<TopBarProgress />}>{children}</Suspense>
|
||||
}
|
||||
});
|
||||
return <Suspense fallback={<TopBarProgress />}>{children}</Suspense>;
|
||||
};
|
||||
|
||||
export {PrivateRoutes}
|
||||
export { PrivateRoutes };
|
||||
|
||||
@@ -24,7 +24,9 @@ type ModalNames = {
|
||||
}
|
||||
|
||||
const MODALNAMES:ModalNames = {
|
||||
addSignatory: 'add signatory'
|
||||
addSignatory: 'add signatory',
|
||||
editSignatory: 'edit signatory',
|
||||
editEmployer: 'edit employer',
|
||||
}
|
||||
|
||||
const CustomModalContext = createContext<ContextProps>({
|
||||
|
||||
Reference in New Issue
Block a user