From 50b12f2da4220719e9a9b1b54099f71060e75ff7 Mon Sep 17 00:00:00 2001 From: victorAnumudu Date: Thu, 6 Jun 2024 17:15:12 +0100 Subject: [PATCH] added employer list and signatory API --- src/_digifi/helpers/crud-helper/consts.ts | 2 + src/_digifi/helpers/crud-helper/models.ts | 5 +- src/_digifi/helpers/formatNumbers.ts | 6 + .../sidebar/sidebar-menu/SidebarMenuMain.tsx | 6 + .../employers/employers-list/UsersPage.tsx | 10 + .../signatory-list/UsersList.tsx | 39 ++ .../add-signatory-modal/AddSignatoryModal.tsx | 44 ++ .../AddSignatoryModalForm.tsx | 407 ++++++++++++++++++ .../AddSignatoryModalFormWrapper.tsx | 40 ++ .../AddSignatoryModalHeader.tsx | 27 ++ .../components/header/UserListToolbar.tsx | 32 ++ .../components/header/UsersListFilter.tsx | 133 ++++++ .../components/header/UsersListGrouping.tsx | 38 ++ .../components/header/UsersListHeader.tsx | 22 + .../header/UsersListSearchComponent.tsx | 45 ++ .../components/loading/UsersListLoading.tsx | 18 + .../pagination/UsersListPagination.tsx | 156 +++++++ .../signatory-list/core/ListViewProvider.tsx | 51 +++ .../core/QueryRequestProvider.tsx | 28 ++ .../core/QueryResponseProvider.tsx | 93 ++++ .../signatory-list/core/_models.ts | 34 ++ .../signatory-list/core/_requests.ts | 59 +++ .../signatory-list/table/UsersTable.tsx | 62 +++ .../table/columns/AddedCell.tsx | 12 + .../table/columns/AgentCell.tsx | 11 + .../table/columns/CustomHeaderColumn.tsx | 15 + .../table/columns/CustomRow.tsx | 25 ++ .../signatory-list/table/columns/Phone.tsx | 11 + .../table/columns/UserActionsCell.tsx | 84 ++++ .../table/columns/UserCustomHeader.tsx | 61 +++ .../table/columns/UserInfoCell.tsx | 42 ++ .../table/columns/UserLastLoginCell.tsx | 11 + .../table/columns/UserSelectionCell.tsx | 26 ++ .../table/columns/UserSelectionHeader.tsx | 28 ++ .../signatory-list/table/columns/_columns.tsx | 58 +++ .../user-edit-modal/UserEditModal.tsx | 44 ++ .../user-edit-modal/UserEditModalForm.tsx | 407 ++++++++++++++++++ .../UserEditModalFormWrapper.tsx | 40 ++ .../user-edit-modal/UserEditModalHeader.tsx | 27 ++ .../employers-list/users-list/UsersList.tsx | 4 +- .../components/header/UserListToolbar.tsx | 12 +- .../users-list/core/QueryResponseProvider.tsx | 16 +- .../employers-list/users-list/core/_models.ts | 18 +- .../users-list/core/_requests.ts | 6 +- .../users-list/table/columns/MaxLoan.tsx | 12 + .../users-list/table/columns/UserInfoCell.tsx | 4 +- .../users-list/table/columns/_columns.tsx | 26 +- 47 files changed, 2320 insertions(+), 37 deletions(-) create mode 100644 src/_digifi/helpers/formatNumbers.ts create mode 100644 src/app/modules/employers/employers-list/signatory-list/UsersList.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModal.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalForm.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalFormWrapper.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalHeader.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/header/UserListToolbar.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/header/UsersListFilter.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/header/UsersListGrouping.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/header/UsersListHeader.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/header/UsersListSearchComponent.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/loading/UsersListLoading.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/components/pagination/UsersListPagination.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/core/ListViewProvider.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/core/QueryRequestProvider.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/core/QueryResponseProvider.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/core/_models.ts create mode 100644 src/app/modules/employers/employers-list/signatory-list/core/_requests.ts create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/UsersTable.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/AddedCell.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/AgentCell.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/CustomHeaderColumn.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/CustomRow.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/Phone.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/UserActionsCell.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/UserCustomHeader.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/UserInfoCell.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/UserLastLoginCell.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionCell.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionHeader.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/table/columns/_columns.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModal.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalForm.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalFormWrapper.tsx create mode 100644 src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalHeader.tsx create mode 100644 src/app/modules/employers/employers-list/users-list/table/columns/MaxLoan.tsx diff --git a/src/_digifi/helpers/crud-helper/consts.ts b/src/_digifi/helpers/crud-helper/consts.ts index 8c09903..cc492b7 100644 --- a/src/_digifi/helpers/crud-helper/consts.ts +++ b/src/_digifi/helpers/crud-helper/consts.ts @@ -1,5 +1,7 @@ const QUERIES = { USERS_LIST: 'users-list', + EMPLOYERS_LIST: 'employers-list', + SIGNATORY_LIST: 'signatory-list', } export {QUERIES} diff --git a/src/_digifi/helpers/crud-helper/models.ts b/src/_digifi/helpers/crud-helper/models.ts index e1996de..fdd62b6 100644 --- a/src/_digifi/helpers/crud-helper/models.ts +++ b/src/_digifi/helpers/crud-helper/models.ts @@ -22,8 +22,11 @@ export type SearchState = { } export type Response = { + call_return? : string | any data?: T - records?: Array + records?: T + salary_sources?: Array + employer_sector?: Array payload?: { message?: string errors?: { diff --git a/src/_digifi/helpers/formatNumbers.ts b/src/_digifi/helpers/formatNumbers.ts new file mode 100644 index 0000000..05be6d5 --- /dev/null +++ b/src/_digifi/helpers/formatNumbers.ts @@ -0,0 +1,6 @@ +export const formatNumbers = (number: string | undefined): string | null => { + if(!number){ + return null + } + return number.replace(/\B(?=(\d{3})+(?!\d))/g, ','); +}; diff --git a/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx b/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx index bb13bcd..4946a7f 100644 --- a/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx +++ b/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx @@ -121,6 +121,12 @@ const SidebarMenuMain = () => { title='List' fontIcon='bi-layers' /> + {/*
*/} {/* = [ { @@ -30,6 +31,15 @@ const EmployersPage = () => { } /> + + Signatory list + + + } + /> } /> diff --git a/src/app/modules/employers/employers-list/signatory-list/UsersList.tsx b/src/app/modules/employers/employers-list/signatory-list/UsersList.tsx new file mode 100644 index 0000000..986a141 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/UsersList.tsx @@ -0,0 +1,39 @@ +import {ListViewProvider, useListView} from './core/ListViewProvider' +import {QueryRequestProvider} from './core/QueryRequestProvider' +import {QueryResponseProvider, useAllResponse} from './core/QueryResponseProvider' +import {UsersListHeader} from './components/header/UsersListHeader' +import {UsersTable} from './table/UsersTable' +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' + +const UsersList = () => { + const response = useAllResponse() + console.log('RESPONSE', response) + const {itemIdForUpdate} = useListView() + return ( + <> + + + + + {itemIdForUpdate !== undefined && } + + ) +} + +const UsersListWrapper = () => ( + + + + + + + + + + +) + +export {UsersListWrapper} diff --git a/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModal.tsx b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModal.tsx new file mode 100644 index 0000000..0a45872 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModal.tsx @@ -0,0 +1,44 @@ +import {useEffect} from 'react' +import {AddSignatoryModalHeader} from './AddSignatoryModalHeader' +import {AddSignatoryModalFormWrapper} from './AddSignatoryModalFormWrapper' + +const AddSignatoryModal = () => { + useEffect(() => { + document.body.classList.add('modal-open') + return () => { + document.body.classList.remove('modal-open') + } + }, []) + + return ( + <> + + {/* begin::Modal Backdrop */} +
+ {/* end::Modal Backdrop */} + + ) +} + +export {AddSignatoryModal} diff --git a/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalForm.tsx b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalForm.tsx new file mode 100644 index 0000000..e703115 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalForm.tsx @@ -0,0 +1,407 @@ +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 AddSignatoryModalForm: FC = ({user, isUserLoading}) => { + const {setItemIdForUpdate} = useListView() + const {refetch} = useQueryResponse() + + const [userForEdit] = useState({ + ...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 ( + <> +
+ {/* begin::Scroll */} +
+ {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + + {/* begin::Image input */} +
+ {/* begin::Preview existing avatar */} +
+ {/* end::Preview existing avatar */} + + {/* begin::Label */} + {/* */} + {/* end::Label */} + + {/* begin::Cancel */} + {/* + + */} + {/* end::Cancel */} + + {/* begin::Remove */} + {/* + + */} + {/* end::Remove */} +
+ {/* end::Image input */} + + {/* begin::Hint */} + {/*
Allowed file types: png, jpg, jpeg.
*/} + {/* end::Hint */} +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + + {/* begin::Input */} + + {formik.touched.name && formik.errors.name && ( +
+
+ {formik.errors.name} +
+
+ )} + {/* end::Input */} +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + + {/* begin::Input */} + + {/* end::Input */} + {formik.touched.email && formik.errors.email && ( +
+ {formik.errors.email} +
+ )} +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + {/* begin::Roles */} + {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} + {/* end::Roles */} +
+ {/* end::Input group */} +
+ {/* end::Scroll */} + + {/* begin::Actions */} +
+ + + +
+ {/* end::Actions */} +
+ {(formik.isSubmitting || isUserLoading) && } + + ) +} + +export {AddSignatoryModalForm} diff --git a/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalFormWrapper.tsx b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalFormWrapper.tsx new file mode 100644 index 0000000..2b87e65 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalFormWrapper.tsx @@ -0,0 +1,40 @@ +import {useQuery} from 'react-query' +import {AddSignatoryModalForm} from './AddSignatoryModalForm' +import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers' +import {useListView} from '../core/ListViewProvider' +import {getUserById} from '../core/_requests' + +const AddSignatoryModalFormWrapper = () => { + const {itemIdForUpdate, setItemIdForUpdate} = useListView() + const enabledQuery: boolean = isNotEmpty(itemIdForUpdate) + 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) + }, + } + ) + + if (!itemIdForUpdate) { + return + } + + if (!isLoading && !error && user) { + return + } + + return null +} + +export {AddSignatoryModalFormWrapper} diff --git a/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalHeader.tsx b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalHeader.tsx new file mode 100644 index 0000000..da0d187 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/add-signatory-modal/AddSignatoryModalHeader.tsx @@ -0,0 +1,27 @@ +import {KTIcon} from '../../../../../../_digifi/helpers' +import {useListView} from '../core/ListViewProvider' + +const AddSignatoryModalHeader = () => { + const {setItemIdForUpdate} = useListView() + + return ( +
+ {/* begin::Modal title */} +

Add User

+ {/* end::Modal title */} + + {/* begin::Close */} +
setItemIdForUpdate(undefined)} + style={{cursor: 'pointer'}} + > + +
+ {/* end::Close */} +
+ ) +} + +export {AddSignatoryModalHeader} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/header/UserListToolbar.tsx b/src/app/modules/employers/employers-list/signatory-list/components/header/UserListToolbar.tsx new file mode 100644 index 0000000..d58256a --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/header/UserListToolbar.tsx @@ -0,0 +1,32 @@ +import {KTIcon} from '../../../../../../../_digifi/helpers' +import {useListView} from '../../core/ListViewProvider' +import {UsersListFilter} from './UsersListFilter' + +const UsersListToolbar = () => { + const {setItemIdForUpdate} = useListView() + const openAddUserModal = () => { + setItemIdForUpdate(null) + } + + return ( +
+ {/* begin::Export */} + {/* */} + {/* end::Export */} + + {/* begin::Add user */} + {/* */} + {/* end::Add user */} + + +
+ ) +} + +export {UsersListToolbar} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListFilter.tsx b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListFilter.tsx new file mode 100644 index 0000000..4ac79a9 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListFilter.tsx @@ -0,0 +1,133 @@ +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() + const [lastLogin, setLastLogin] = useState() + + useEffect(() => { + MenuComponent.reinitialization() + }, []) + + const resetData = () => { + updateState({filter: undefined, ...initialQueryState}) + } + + const filterData = () => { + updateState({ + filter: {role, last_login: lastLogin}, + ...initialQueryState, + }) + } + + return ( + <> + {/* begin::Filter Button */} + + {/* end::Filter Button */} + {/* begin::SubMenu */} +
+ {/* begin::Header */} +
+
Filter Options
+
+ {/* end::Header */} + + {/* begin::Separator */} +
+ {/* end::Separator */} + + {/* begin::Content */} +
+ {/* begin::Input group */} +
+ + +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ + +
+ {/* end::Input group */} + + {/* begin::Actions */} +
+ + +
+ {/* end::Actions */} +
+ {/* end::Content */} +
+ {/* end::SubMenu */} + + ) +} + +export {UsersListFilter} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListGrouping.tsx b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListGrouping.tsx new file mode 100644 index 0000000..6400fd7 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListGrouping.tsx @@ -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 ( +
+
+ {selected.length} Selected +
+ + +
+ ) +} + +export {UsersListGrouping} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListHeader.tsx b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListHeader.tsx new file mode 100644 index 0000000..9d2a3a0 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListHeader.tsx @@ -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 ( +
+ + {/* begin::Card toolbar */} +
+ {/* begin::Group actions */} + {selected.length > 0 ? : } + {/* end::Group actions */} +
+ {/* end::Card toolbar */} +
+ ) +} + +export {UsersListHeader} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListSearchComponent.tsx b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListSearchComponent.tsx new file mode 100644 index 0000000..75cba1c --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/header/UsersListSearchComponent.tsx @@ -0,0 +1,45 @@ +/* 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('') + // 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 ( +
+ {/* begin::Search */} +
+ + setSearchTerm(e.target.value)} + /> +
+ {/* end::Search */} +
+ ) +} + +export {UsersListSearchComponent} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/loading/UsersListLoading.tsx b/src/app/modules/employers/employers-list/signatory-list/components/loading/UsersListLoading.tsx new file mode 100644 index 0000000..2278f87 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/loading/UsersListLoading.tsx @@ -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
Processing...
+} + +export {UsersListLoading} diff --git a/src/app/modules/employers/employers-list/signatory-list/components/pagination/UsersListPagination.tsx b/src/app/modules/employers/employers-list/signatory-list/components/pagination/UsersListPagination.tsx new file mode 100644 index 0000000..2879f48 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/components/pagination/UsersListPagination.tsx @@ -0,0 +1,156 @@ + +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 ( + + ) +} + +export {UsersListPagination} diff --git a/src/app/modules/employers/employers-list/signatory-list/core/ListViewProvider.tsx b/src/app/modules/employers/employers-list/signatory-list/core/ListViewProvider.tsx new file mode 100644 index 0000000..91cc2cf --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/core/ListViewProvider.tsx @@ -0,0 +1,51 @@ +/* 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(initialListView) + +const ListViewProvider: FC = ({children}) => { + const [selected, setSelected] = useState>(initialListView.selected) + const [itemIdForUpdate, setItemIdForUpdate] = useState(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 ( + { + groupingOnSelect(id, selected, setSelected) + }, + onSelectAll: () => { + groupingOnSelectAll(isAllSelected, setSelected, data) + }, + clearSelected: () => { + setSelected([]) + }, + }} + > + {children} + + ) +} + +const useListView = () => useContext(ListViewContext) + +export {ListViewProvider, useListView} diff --git a/src/app/modules/employers/employers-list/signatory-list/core/QueryRequestProvider.tsx b/src/app/modules/employers/employers-list/signatory-list/core/QueryRequestProvider.tsx new file mode 100644 index 0000000..95f31cf --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/core/QueryRequestProvider.tsx @@ -0,0 +1,28 @@ +/* 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(initialQueryRequest) + +const QueryRequestProvider: FC = ({children}) => { + const [state, setState] = useState(initialQueryRequest.state) + + const updateState = (updates: Partial) => { + const updatedState = {...state, ...updates} as QueryState + setState(updatedState) + } + + return ( + + {children} + + ) +} + +const useQueryRequest = () => useContext(QueryRequestContext) +export {QueryRequestProvider, useQueryRequest} diff --git a/src/app/modules/employers/employers-list/signatory-list/core/QueryResponseProvider.tsx b/src/app/modules/employers/employers-list/signatory-list/core/QueryResponseProvider.tsx new file mode 100644 index 0000000..c25c641 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/core/QueryResponseProvider.tsx @@ -0,0 +1,93 @@ +/* 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 {getSignatoryList} from './_requests' +import {User} from './_models' +import {useQueryRequest} from './QueryRequestProvider' + +const QueryResponseContext = createResponseContext(initialQueryResponse) +const QueryResponseProvider: FC = ({children}) => { + const {state} = useQueryRequest() + const [query, setQuery] = useState(stringifyRequestQuery(state)) + const updatedQuery = useMemo(() => stringifyRequestQuery(state), [state]) + + useEffect(() => { + if (query !== updatedQuery) { + setQuery(updatedQuery) + } + }, [updatedQuery]) + + const { + isFetching, + refetch, + data: response, + } = useQuery( + `${QUERIES.SIGNATORY_LIST}-${query}`, + () => { + return getSignatoryList(query) + }, + {cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false} + ) + + return ( + + {children} + + ) +} + +const useQueryResponse = () => useContext(QueryResponseContext) + +const useQueryResponseData = () => { + const {response} = useQueryResponse() + if (!response) { + return [] + } + return response?.records || [] +} + +const useAllResponse = () => { + const {response} = useQueryResponse() + if (!response) { + return [] + } + return response || [] +} + +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, + useAllResponse +} diff --git a/src/app/modules/employers/employers-list/signatory-list/core/_models.ts b/src/app/modules/employers/employers-list/signatory-list/core/_models.ts new file mode 100644 index 0000000..bbd02c6 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/core/_models.ts @@ -0,0 +1,34 @@ +import {ID, Response} from '../../../../../../_digifi/helpers' +export type User = { + id?: ID + name?: string + avatar?: string + // email?: string + position?: string + role?: string + last_login?: string + two_steps?: boolean + joined_day?: string + online?: boolean + initials?: { + label: string + state: string + } + uid?: string + added?: string + updated?: string + email?: string + employer_name?: string + title?: string + phone?: string +} + +export type UsersQueryResponse = Response> + +export const initialUser: User = { + avatar: 'avatars/300-6.jpg', + position: 'Art Director', + role: 'Administrator', + name: '', + email: '', +} diff --git a/src/app/modules/employers/employers-list/signatory-list/core/_requests.ts b/src/app/modules/employers/employers-list/signatory-list/core/_requests.ts new file mode 100644 index 0000000..ae3e56f --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/core/_requests.ts @@ -0,0 +1,59 @@ +import axios, { AxiosResponse } from "axios"; +import { ID, Response } from "../../../../../../_digifi/helpers"; +import { User, UsersQueryResponse } from "./_models"; + +const API_URL = import.meta.env.VITE_APP_THEME_API_URL; +const USER_URL = `${API_URL}/user`; +// const GET_USERS_URL = `${API_URL}/users/query`; + +const NEW_USER_ENDPOINT = import.meta.env.VITE_APP_USER_ENDPOINT + +// const getStartedUsers = (query: string): Promise => { +// return axios +// .get(`${GET_USERS_URL}?${query}`) +// .then((d: AxiosResponse) => d.data); +// }; +const getSignatoryList = (query: string): Promise => { // FUNCTION TO GET EMPLOYERS LIST + return axios + .get(`${NEW_USER_ENDPOINT}/employers/signatory`) + .then((d: AxiosResponse) => d.data); +}; + +const getUserById = (id: ID): Promise => { + return axios + .get(`${USER_URL}/${id}`) + .then((response: AxiosResponse>) => response.data) + .then((response: Response) => response.data); +}; + +const createUser = (user: User): Promise => { + return axios + .put(USER_URL, user) + .then((response: AxiosResponse>) => response.data) + .then((response: Response) => response.data); +}; + +const updateUser = (user: User): Promise => { + return axios + .post(`${USER_URL}/${user.id}`, user) + .then((response: AxiosResponse>) => response.data) + .then((response: Response) => response.data); +}; + +const deleteUser = (userId: ID): Promise => { + return axios.delete(`${USER_URL}/${userId}`).then(() => {}); +}; + +const deleteSelectedUsers = (userIds: Array): Promise => { + const requests = userIds.map((id) => axios.delete(`${USER_URL}/${id}`)); + return axios.all(requests).then(() => {}); +}; + +export { + getSignatoryList, + deleteUser, + deleteSelectedUsers, + getUserById, + createUser, + updateUser, +}; diff --git a/src/app/modules/employers/employers-list/signatory-list/table/UsersTable.tsx b/src/app/modules/employers/employers-list/signatory-list/table/UsersTable.tsx new file mode 100644 index 0000000..f48b4b4 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/UsersTable.tsx @@ -0,0 +1,62 @@ +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('users', users) + const isLoading = useQueryResponseLoading() + const data = useMemo(() => users, [users]) + const columns = useMemo(() => usersColumns, []) + const {getTableProps, getTableBodyProps, headers, rows, prepareRow} = useTable({ + columns, + data, + }) + + return ( + +
+ + + + {headers.map((column: ColumnInstance) => ( + + ))} + + + + {rows.length > 0 ? ( + rows.map((row: Row, i) => { + prepareRow(row) + return + }) + ) : ( + + + + )} + +
+
+ No matching records found +
+
+
+ + {isLoading && } +
+ ) +} + +export {UsersTable} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/AddedCell.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/AddedCell.tsx new file mode 100644 index 0000000..6f5bc2b --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/AddedCell.tsx @@ -0,0 +1,12 @@ +import {FC} from 'react' +import { NewDateTimeFormatter } from '../../../../../../../_digifi/lib/NewDateTimeFormatter' + +type Props = { + added?: string +} + +const AddedCell: FC = ({added}) => ( +
{NewDateTimeFormatter((added))}
+) + +export {AddedCell} \ No newline at end of file diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/AgentCell.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/AgentCell.tsx new file mode 100644 index 0000000..063ade5 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/AgentCell.tsx @@ -0,0 +1,11 @@ +import {FC} from 'react' + +type Props = { + agent?: string +} + +const AgentCell: FC = ({agent}) => ( + <> {agent &&
{agent}
} +) + +export {AgentCell} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/CustomHeaderColumn.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/CustomHeaderColumn.tsx new file mode 100644 index 0000000..838a12e --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/CustomHeaderColumn.tsx @@ -0,0 +1,15 @@ +import {FC} from 'react' +import {ColumnInstance} from 'react-table' +import {User} from '../../core/_models' + +type Props = { + column: ColumnInstance +} + +const CustomHeaderColumn: FC = ({column}) => ( + <> + {column.Header && typeof column.Header === 'string' ? {column.render('Header')} : column.render('Header')} + +) + +export {CustomHeaderColumn} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/CustomRow.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/CustomRow.tsx new file mode 100644 index 0000000..a869cfe --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/CustomRow.tsx @@ -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 +} + +const CustomRow: FC = ({row}) => ( + + {row.cells.map((cell) => { + return ( + + {cell.render('Cell')} + + ) + })} + +) + +export {CustomRow} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/Phone.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/Phone.tsx new file mode 100644 index 0000000..7e71840 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/Phone.tsx @@ -0,0 +1,11 @@ +import {FC} from 'react' + +type Props = { + phone?: string +} + +const Phone: FC = ({phone}) => ( +
{phone}
+) + +export {Phone} \ No newline at end of file diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/UserActionsCell.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserActionsCell.tsx new file mode 100644 index 0000000..ead87e4 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserActionsCell.tsx @@ -0,0 +1,84 @@ + +import {FC, useEffect} from 'react' +import {useMutation, 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 {deleteUser} from '../../core/_requests' + +type Props = { + id: ID +} + +const UserActionsCell: FC = ({id}) => { + const {setItemIdForUpdate} = useListView() + const {query} = useQueryResponse() + const queryClient = useQueryClient() + + useEffect(() => { + MenuComponent.reinitialization() + }, []) + + const openEditModal = () => { + setItemIdForUpdate(id) + } + + const deleteItem = useMutation(() => deleteUser(id), { + // 💡 response of the mutation is passed to onSuccess + onSuccess: () => { + // ✅ update detail view directly + queryClient.invalidateQueries([`${QUERIES.USERS_LIST}-${query}`]) + }, + }) + + return ( + <> + + Actions + + + {/* begin::Menu */} +
+ {/* begin::Menu item */} + + {/* end::Menu item */} + + {/* begin::Menu item */} + + {/* end::Menu item */} + + {/* begin::Menu item */} + + {/* end::Menu item */} +
+ {/* end::Menu */} + + ) +} + +export {UserActionsCell} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/UserCustomHeader.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserCustomHeader.tsx new file mode 100644 index 0000000..3d0b58a --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserCustomHeader.tsx @@ -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> +} +const UserCustomHeader: FC = ({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 ( + + {title} + + ) +} + +export {UserCustomHeader} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/UserInfoCell.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserInfoCell.tsx new file mode 100644 index 0000000..63402f8 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserInfoCell.tsx @@ -0,0 +1,42 @@ + +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 = ({user}) => ( + +) + +export {UserInfoCell} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/UserLastLoginCell.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserLastLoginCell.tsx new file mode 100644 index 0000000..a8a0ebe --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserLastLoginCell.tsx @@ -0,0 +1,11 @@ +import {FC} from 'react' + +type Props = { + payment_month?: string +} + +const PaymentMonthCell: FC = ({payment_month}) => ( +
{payment_month}
+) + +export {PaymentMonthCell} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionCell.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionCell.tsx new file mode 100644 index 0000000..dcd2bfb --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionCell.tsx @@ -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 = ({id}) => { + const {selected, onSelect} = useListView() + const isSelected = useMemo(() => selected.includes(id), [id, selected]) + return ( +
+ onSelect(id)} + /> +
+ ) +} + +export {UserSelectionCell} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionHeader.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionHeader.tsx new file mode 100644 index 0000000..bbb1eb0 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/UserSelectionHeader.tsx @@ -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> +} + +const UserSelectionHeader: FC = ({tableProps}) => { + const {isAllSelected, onSelectAll} = useListView() + return ( + +
+ +
+ + ) +} + +export {UserSelectionHeader} diff --git a/src/app/modules/employers/employers-list/signatory-list/table/columns/_columns.tsx b/src/app/modules/employers/employers-list/signatory-list/table/columns/_columns.tsx new file mode 100644 index 0000000..67e7c31 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/table/columns/_columns.tsx @@ -0,0 +1,58 @@ +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 { Phone } from './Phone' + +const usersColumns: ReadonlyArray> = [ + { + Header: (props) => , + id: 'selection', + Cell: ({...props}) => , + }, + { + Header: (props) => , + id: 'name', + Cell: ({...props}) => , + }, + // { + // Header: (props) => , + // accessor: 'max_loan', + // }, + { + Header: (props) => ( + + ), + id: 'phone', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'employer_name', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'added', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'actions', + Cell: ({...props}) => , + }, +] + +export {usersColumns} \ No newline at end of file diff --git a/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModal.tsx b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModal.tsx new file mode 100644 index 0000000..9bf605f --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModal.tsx @@ -0,0 +1,44 @@ +import {useEffect} from 'react' +import {UserEditModalHeader} from './UserEditModalHeader' +import {UserEditModalFormWrapper} from './UserEditModalFormWrapper' + +const UserEditModal = () => { + useEffect(() => { + document.body.classList.add('modal-open') + return () => { + document.body.classList.remove('modal-open') + } + }, []) + + return ( + <> + + {/* begin::Modal Backdrop */} +
+ {/* end::Modal Backdrop */} + + ) +} + +export {UserEditModal} diff --git a/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalForm.tsx b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalForm.tsx new file mode 100644 index 0000000..6e8b0ec --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalForm.tsx @@ -0,0 +1,407 @@ +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 = ({user, isUserLoading}) => { + const {setItemIdForUpdate} = useListView() + const {refetch} = useQueryResponse() + + const [userForEdit] = useState({ + ...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 ( + <> +
+ {/* begin::Scroll */} +
+ {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + + {/* begin::Image input */} +
+ {/* begin::Preview existing avatar */} +
+ {/* end::Preview existing avatar */} + + {/* begin::Label */} + {/* */} + {/* end::Label */} + + {/* begin::Cancel */} + {/* + + */} + {/* end::Cancel */} + + {/* begin::Remove */} + {/* + + */} + {/* end::Remove */} +
+ {/* end::Image input */} + + {/* begin::Hint */} + {/*
Allowed file types: png, jpg, jpeg.
*/} + {/* end::Hint */} +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + + {/* begin::Input */} + + {formik.touched.name && formik.errors.name && ( +
+
+ {formik.errors.name} +
+
+ )} + {/* end::Input */} +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + + {/* begin::Input */} + + {/* end::Input */} + {formik.touched.email && formik.errors.email && ( +
+ {formik.errors.email} +
+ )} +
+ {/* end::Input group */} + + {/* begin::Input group */} +
+ {/* begin::Label */} + + {/* end::Label */} + {/* begin::Roles */} + {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} +
+ {/* begin::Input row */} +
+ {/* begin::Radio */} +
+ {/* begin::Input */} + + {/* end::Input */} + {/* begin::Label */} + + {/* end::Label */} +
+ {/* end::Radio */} +
+ {/* end::Input row */} + {/* end::Roles */} +
+ {/* end::Input group */} +
+ {/* end::Scroll */} + + {/* begin::Actions */} +
+ + + +
+ {/* end::Actions */} +
+ {(formik.isSubmitting || isUserLoading) && } + + ) +} + +export {UserEditModalForm} diff --git a/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalFormWrapper.tsx b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalFormWrapper.tsx new file mode 100644 index 0000000..d60d7e3 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalFormWrapper.tsx @@ -0,0 +1,40 @@ +import {useQuery} from 'react-query' +import {UserEditModalForm} from './UserEditModalForm' +import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers' +import {useListView} from '../core/ListViewProvider' +import {getUserById} from '../core/_requests' + +const UserEditModalFormWrapper = () => { + const {itemIdForUpdate, setItemIdForUpdate} = useListView() + const enabledQuery: boolean = isNotEmpty(itemIdForUpdate) + 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) + }, + } + ) + + if (!itemIdForUpdate) { + return + } + + if (!isLoading && !error && user) { + return + } + + return null +} + +export {UserEditModalFormWrapper} diff --git a/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalHeader.tsx b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalHeader.tsx new file mode 100644 index 0000000..cb0f5b6 --- /dev/null +++ b/src/app/modules/employers/employers-list/signatory-list/user-edit-modal/UserEditModalHeader.tsx @@ -0,0 +1,27 @@ +import {KTIcon} from '../../../../../../_digifi/helpers' +import {useListView} from '../core/ListViewProvider' + +const UserEditModalHeader = () => { + const {setItemIdForUpdate} = useListView() + + return ( +
+ {/* begin::Modal title */} +

Add User

+ {/* end::Modal title */} + + {/* begin::Close */} +
setItemIdForUpdate(undefined)} + style={{cursor: 'pointer'}} + > + +
+ {/* end::Close */} +
+ ) +} + +export {UserEditModalHeader} diff --git a/src/app/modules/employers/employers-list/users-list/UsersList.tsx b/src/app/modules/employers/employers-list/users-list/UsersList.tsx index d8f1f10..986a141 100644 --- a/src/app/modules/employers/employers-list/users-list/UsersList.tsx +++ b/src/app/modules/employers/employers-list/users-list/UsersList.tsx @@ -1,6 +1,6 @@ import {ListViewProvider, useListView} from './core/ListViewProvider' import {QueryRequestProvider} from './core/QueryRequestProvider' -import {QueryResponseProvider} from './core/QueryResponseProvider' +import {QueryResponseProvider, useAllResponse} from './core/QueryResponseProvider' import {UsersListHeader} from './components/header/UsersListHeader' import {UsersTable} from './table/UsersTable' import {UserEditModal} from './user-edit-modal/UserEditModal' @@ -9,6 +9,8 @@ import { ToolbarWrapper } from '../../../../../_digifi/layout/components/toolbar import { Content } from '../../../../../_digifi/layout/components/content' const UsersList = () => { + const response = useAllResponse() + console.log('RESPONSE', response) const {itemIdForUpdate} = useListView() return ( <> diff --git a/src/app/modules/employers/employers-list/users-list/components/header/UserListToolbar.tsx b/src/app/modules/employers/employers-list/users-list/components/header/UserListToolbar.tsx index e86343b..82ab632 100644 --- a/src/app/modules/employers/employers-list/users-list/components/header/UserListToolbar.tsx +++ b/src/app/modules/employers/employers-list/users-list/components/header/UserListToolbar.tsx @@ -10,9 +10,7 @@ const UsersListToolbar = () => { return (
- - - {/* begin::Export */} + {/* begin::Export */} {/* */} + Add New + {/* end::Add user */} + +
) } diff --git a/src/app/modules/employers/employers-list/users-list/core/QueryResponseProvider.tsx b/src/app/modules/employers/employers-list/users-list/core/QueryResponseProvider.tsx index 3f7cffe..2469585 100644 --- a/src/app/modules/employers/employers-list/users-list/core/QueryResponseProvider.tsx +++ b/src/app/modules/employers/employers-list/users-list/core/QueryResponseProvider.tsx @@ -11,7 +11,7 @@ import { stringifyRequestQuery, WithChildren, } from '../../../../../../_digifi/helpers' -import {getStartedUsers} from './_requests' +import {getEmployersList} from './_requests' import {User} from './_models' import {useQueryRequest} from './QueryRequestProvider' @@ -32,9 +32,9 @@ const QueryResponseProvider: FC = ({children}) => { refetch, data: response, } = useQuery( - `${QUERIES.USERS_LIST}-${query}`, + `${QUERIES.EMPLOYERS_LIST}-${query}`, () => { - return getStartedUsers(query) + return getEmployersList(query) }, {cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false} ) @@ -53,10 +53,17 @@ const useQueryResponseData = () => { if (!response) { return [] } - return response?.records || [] } +const useAllResponse = () => { + const {response} = useQueryResponse() + if (!response) { + return [] + } + return response || [] +} + const useQueryResponsePagination = () => { const defaultPaginationState: PaginationState = { links: [], @@ -82,4 +89,5 @@ export { useQueryResponseData, useQueryResponsePagination, useQueryResponseLoading, + useAllResponse } diff --git a/src/app/modules/employers/employers-list/users-list/core/_models.ts b/src/app/modules/employers/employers-list/users-list/core/_models.ts index b9b06ec..8b7a211 100644 --- a/src/app/modules/employers/employers-list/users-list/core/_models.ts +++ b/src/app/modules/employers/employers-list/users-list/core/_models.ts @@ -14,21 +14,17 @@ export type User = { label: string state: string } - firstname?: string, - lastname?: string uid?: string - loan_amount?: string - payment_month?: string - sales_agent?: string - gender?: string | null - marital_status?: string - email?: string - address?: string - state?: string - country?: string + percent_interest?: string + max_loan?: string + tenor?: string + retirement_age?: string status?: string + sector?: string + salary_source?: string added?: string updated?: string + email?: string } export type UsersQueryResponse = Response> diff --git a/src/app/modules/employers/employers-list/users-list/core/_requests.ts b/src/app/modules/employers/employers-list/users-list/core/_requests.ts index a585521..5e26c09 100644 --- a/src/app/modules/employers/employers-list/users-list/core/_requests.ts +++ b/src/app/modules/employers/employers-list/users-list/core/_requests.ts @@ -13,9 +13,9 @@ const NEW_USER_ENDPOINT = import.meta.env.VITE_APP_USER_ENDPOINT // .get(`${GET_USERS_URL}?${query}`) // .then((d: AxiosResponse) => d.data); // }; -const getStartedUsers = (query: string): Promise => { // FUNCTION TO GET USERS THAT HAVE STARTED LOAN APPLICATION +const getEmployersList = (query: string): Promise => { // FUNCTION TO GET EMPLOYERS LIST return axios - .get(`${NEW_USER_ENDPOINT}/loan/started`) + .get(`${NEW_USER_ENDPOINT}/employers`) .then((d: AxiosResponse) => d.data); }; @@ -50,7 +50,7 @@ const deleteSelectedUsers = (userIds: Array): Promise => { }; export { - getStartedUsers, + getEmployersList, deleteUser, deleteSelectedUsers, getUserById, diff --git a/src/app/modules/employers/employers-list/users-list/table/columns/MaxLoan.tsx b/src/app/modules/employers/employers-list/users-list/table/columns/MaxLoan.tsx new file mode 100644 index 0000000..b8ec306 --- /dev/null +++ b/src/app/modules/employers/employers-list/users-list/table/columns/MaxLoan.tsx @@ -0,0 +1,12 @@ +import {FC} from 'react' +import { formatNumbers } from '../../../../../../../_digifi/helpers/formatNumbers' + +type Props = { + max_loan?: string +} + +const MaxLoan: FC = ({max_loan}) => ( +
{formatNumbers(max_loan)}
+) + +export {MaxLoan} \ No newline at end of file diff --git a/src/app/modules/employers/employers-list/users-list/table/columns/UserInfoCell.tsx b/src/app/modules/employers/employers-list/users-list/table/columns/UserInfoCell.tsx index 1840fef..63402f8 100644 --- a/src/app/modules/employers/employers-list/users-list/table/columns/UserInfoCell.tsx +++ b/src/app/modules/employers/employers-list/users-list/table/columns/UserInfoCell.tsx @@ -25,14 +25,14 @@ const UserInfoCell: FC = ({user}) => ( `text-${user.initials?.state}` )} > - {user.firstname?.substring(0,1).toUpperCase()} {user.lastname?.substring(0,1).toUpperCase()} + {user.name?.substring(0,1).toUpperCase()} {user.name?.substring(0,1).toUpperCase()}
)} diff --git a/src/app/modules/employers/employers-list/users-list/table/columns/_columns.tsx b/src/app/modules/employers/employers-list/users-list/table/columns/_columns.tsx index 897f37a..eba8873 100644 --- a/src/app/modules/employers/employers-list/users-list/table/columns/_columns.tsx +++ b/src/app/modules/employers/employers-list/users-list/table/columns/_columns.tsx @@ -8,6 +8,7 @@ import {UserCustomHeader} from './UserCustomHeader' import {UserSelectionHeader} from './UserSelectionHeader' import {User} from '../../core/_models' import { AddedCell } from './AddedCell' +import { MaxLoan } from './MaxLoan' const usersColumns: ReadonlyArray> = [ { @@ -17,26 +18,33 @@ const usersColumns: ReadonlyArray> = [ }, { Header: (props) => , - id: 'firstname', + id: 'name', Cell: ({...props}) => , }, + // { + // Header: (props) => , + // accessor: 'max_loan', + // }, { - Header: (props) => , - accessor: 'loan_amount', + Header: (props) => ( + + ), + id: 'max_loan', + Cell: ({...props}) => , }, { Header: (props) => ( - + ), - id: 'payment_month', - Cell: ({...props}) => , + id: 'percent_interest', + Cell: ({...props}) => , }, { Header: (props) => ( - + ), - id: 'sales_agent', - Cell: ({...props}) => , + id: 'retirement_age', + Cell: ({...props}) => , }, { Header: (props) => ( -- 2.34.1