Compare commits

...

4 Commits

Author SHA1 Message Date
victorAnumudu a26b9f5d11 edit signatory submit button renamed 2024-06-20 12:09:27 +01:00
ameye 35dcc853b8 Merge branch 'edit-signatory-modal' of DigiFi/digifi-bko into master 2024-06-19 23:16:31 +00:00
victorAnumudu 368ca22e62 edit signatory modal added 2024-06-19 20:34:49 +01:00
ameye c1c24db83d Merge branch 'create-signatory-update' of DigiFi/digifi-bko into master 2024-06-16 03:30:49 +00:00
26 changed files with 1309 additions and 37 deletions
@@ -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'}}
>
&lt; 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 &gt;
</button>
</div>
)}
</div>
);
}
@@ -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(() => {});
@@ -1,9 +1,9 @@
import {useEffect} from 'react'
import {UserEditModalHeader} from './UserEditModalHeader'
import {UserEditModalFormWrapper} from './UserEditModalFormWrapper'
import { ModalHeader } from './ModalHeader'
import { ModalFormWrapper } from './ModalFormWrapper'
const UserEditModal = () => {
const Modal = () => {
useEffect(() => {
document.body.classList.add('modal-open')
@@ -25,10 +25,10 @@ const UserEditModal = () => {
<div className='modal-dialog modal-dialog-centered mw-650px'>
{/* begin::Modal content */}
<div className='modal-content'>
<UserEditModalHeader />
<ModalHeader />
{/* begin::Modal body */}
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
<UserEditModalFormWrapper />
<ModalFormWrapper />
</div>
{/* end::Modal body */}
</div>
@@ -43,4 +43,4 @@ const UserEditModal = () => {
)
}
export {UserEditModal}
export {Modal}
@@ -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 };
@@ -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}
@@ -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}
@@ -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 />}
</>
)
}
@@ -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}
@@ -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 };
@@ -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}
@@ -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
}
@@ -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 };
@@ -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}
@@ -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}
@@ -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}
@@ -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} />,
},
]
+3 -1
View File
@@ -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>({