From 450ae649c3d27d9f8aabfe38275fd649659a18f7 Mon Sep 17 00:00:00 2001 From: victorAnumudu Date: Thu, 11 Jul 2024 17:31:34 +0100 Subject: [PATCH] side bar update --- src/_digifi/helpers/crud-helper/consts.ts | 1 + .../sidebar-menu/SidebarMenuItemWithSub.tsx | 4 +- .../sidebar/sidebar-menu/SidebarMenuMain.tsx | 8 +- src/app/modules/process/ProcessPage.tsx | 10 + .../process/components/UserVerifiedList.tsx | 5 + src/app/modules/process/core/_requests.ts | 7 + .../process/user-verified/UsersList.tsx | 37 ++ .../components/header/UserListToolbar.tsx | 35 ++ .../components/header/UsersListFilter.tsx | 136 ++++++ .../components/header/UsersListGrouping.tsx | 38 ++ .../components/header/UsersListHeader.tsx | 22 + .../header/UsersListSearchComponent.tsx | 49 ++ .../components/loading/UsersListLoading.tsx | 18 + .../pagination/UsersListPagination.tsx | 179 ++++++++ .../user-verified/core/ListViewProvider.tsx | 62 +++ .../core/QueryRequestProvider.tsx | 29 ++ .../core/QueryResponseProvider.tsx | 87 ++++ .../edit-loan-modal/EditLoanModal.tsx | 44 ++ .../edit-loan-modal/UserEditModalForm.tsx | 434 ++++++++++++++++++ .../UserEditModalFormWrapper.tsx | 46 ++ .../edit-loan-modal/UserEditModalHeader.tsx | 27 ++ .../user-verified/table/UsersTable.tsx | 66 +++ .../user-verified/table/columns/AddedCell.tsx | 14 + .../user-verified/table/columns/AgentCell.tsx | 11 + .../table/columns/CustomHeaderColumn.tsx | 15 + .../user-verified/table/columns/CustomRow.tsx | 25 + .../table/columns/EmployerCell.tsx | 11 + .../table/columns/UserActionsCell.tsx | 85 ++++ .../table/columns/UserCustomHeader.tsx | 61 +++ .../table/columns/UserInfoCell.tsx | 46 ++ .../table/columns/UserLastLoginCell.tsx | 11 + .../table/columns/UserSelectionCell.tsx | 26 ++ .../table/columns/UserSelectionHeader.tsx | 28 ++ .../user-verified/table/columns/_columns.tsx | 65 +++ 34 files changed, 1738 insertions(+), 4 deletions(-) create mode 100644 src/app/modules/process/components/UserVerifiedList.tsx create mode 100644 src/app/modules/process/user-verified/UsersList.tsx create mode 100644 src/app/modules/process/user-verified/components/header/UserListToolbar.tsx create mode 100644 src/app/modules/process/user-verified/components/header/UsersListFilter.tsx create mode 100644 src/app/modules/process/user-verified/components/header/UsersListGrouping.tsx create mode 100644 src/app/modules/process/user-verified/components/header/UsersListHeader.tsx create mode 100644 src/app/modules/process/user-verified/components/header/UsersListSearchComponent.tsx create mode 100644 src/app/modules/process/user-verified/components/loading/UsersListLoading.tsx create mode 100644 src/app/modules/process/user-verified/components/pagination/UsersListPagination.tsx create mode 100644 src/app/modules/process/user-verified/core/ListViewProvider.tsx create mode 100644 src/app/modules/process/user-verified/core/QueryRequestProvider.tsx create mode 100644 src/app/modules/process/user-verified/core/QueryResponseProvider.tsx create mode 100644 src/app/modules/process/user-verified/edit-loan-modal/EditLoanModal.tsx create mode 100644 src/app/modules/process/user-verified/edit-loan-modal/UserEditModalForm.tsx create mode 100644 src/app/modules/process/user-verified/edit-loan-modal/UserEditModalFormWrapper.tsx create mode 100644 src/app/modules/process/user-verified/edit-loan-modal/UserEditModalHeader.tsx create mode 100644 src/app/modules/process/user-verified/table/UsersTable.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/AddedCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/AgentCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/CustomHeaderColumn.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/CustomRow.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/EmployerCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/UserActionsCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/UserCustomHeader.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/UserInfoCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/UserLastLoginCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/UserSelectionCell.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/UserSelectionHeader.tsx create mode 100644 src/app/modules/process/user-verified/table/columns/_columns.tsx diff --git a/src/_digifi/helpers/crud-helper/consts.ts b/src/_digifi/helpers/crud-helper/consts.ts index 2d0f35f..ad3c2fe 100644 --- a/src/_digifi/helpers/crud-helper/consts.ts +++ b/src/_digifi/helpers/crud-helper/consts.ts @@ -2,6 +2,7 @@ const QUERIES = { USERS_LIST: 'users-list', STARTED_LIST: 'started-list', READY_LIST: 'ready-list', + VERIFIED_LIST: 'verified-list', PENDING_LIST: 'pending-list', APPROVED_LIST: 'approved-list', REJECTED_LIST: 'rejected-list', diff --git a/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuItemWithSub.tsx b/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuItemWithSub.tsx index 3805f35..a675b98 100644 --- a/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuItemWithSub.tsx +++ b/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuItemWithSub.tsx @@ -10,6 +10,7 @@ type Props = { icon?: string fontIcon?: string hasBullet?: boolean + menuIsOpen?: boolean } const SidebarMenuItemWithSub: React.FC = ({ @@ -19,9 +20,10 @@ const SidebarMenuItemWithSub: React.FC = ({ icon, fontIcon, hasBullet, + menuIsOpen=false }) => { const {pathname} = useLocation() - const isActive = checkIsActive(pathname, to) + const isActive = checkIsActive(pathname, to) || menuIsOpen const {config} = useLayout() const {app} = config diff --git a/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx b/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx index 7b1b302..116e501 100644 --- a/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx +++ b/src/_digifi/layout/components/sidebar/sidebar-menu/SidebarMenuMain.tsx @@ -15,18 +15,19 @@ const SidebarMenuMain = () => { fontIcon='bi-app-indicator' /> {/**/} -
+ {/*
Loan
-
+
*/} - + { title='Ready' hasBullet={true} /> + = [ { @@ -64,6 +65,15 @@ const ProcessPage = () => ( } /> + + Verified + + + } + /> ; + +export { UserVerifiedList }; diff --git a/src/app/modules/process/core/_requests.ts b/src/app/modules/process/core/_requests.ts index 5b8dcfb..298d2e2 100644 --- a/src/app/modules/process/core/_requests.ts +++ b/src/app/modules/process/core/_requests.ts @@ -38,6 +38,12 @@ const getReadyUsers = (query: string): Promise => { // FUNCT .then((d: AxiosResponse) => d.data); }; +const getVerifiedUsers = (query: string): Promise => { // FUNCTION TO GET USERS THAT HAVE ARE VERIFIED + return axios + .get(`${NEW_USER_ENDPOINT}/loan/verified`) + .then((d: AxiosResponse) => d.data); +}; + const getApprovedUsers = (query: string): Promise => { // FUNCTION TO GET USERS THAT HAVE APPROVED LOAN APPLICATION return axios .get(`${NEW_USER_ENDPOINT}/loan/approved`) @@ -83,6 +89,7 @@ export { getRejectedUsers, getPendingUsers, getReadyUsers, + getVerifiedUsers, getApprovedUsers, employersVerify, deleteUser, diff --git a/src/app/modules/process/user-verified/UsersList.tsx b/src/app/modules/process/user-verified/UsersList.tsx new file mode 100644 index 0000000..11dd4e9 --- /dev/null +++ b/src/app/modules/process/user-verified/UsersList.tsx @@ -0,0 +1,37 @@ +import { ListViewProvider, useListView } from "./core/ListViewProvider"; +import { QueryRequestProvider } from "./core/QueryRequestProvider"; +import { QueryResponseProvider } from "./core/QueryResponseProvider"; +import { UsersListHeader } from "./components/header/UsersListHeader"; +import { UsersTable } from "./table/UsersTable"; +import { EditLoanModal } from "./edit-loan-modal/EditLoanModal"; +import { KTCard } from "../../../../_digifi/helpers"; +import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar"; +import { Content } from "../../../../_digifi/layout/components/content"; + +const UsersList = () => { + const { itemIdForUpdate } = useListView(); + return ( + <> + + + + + {itemIdForUpdate !== undefined && } + + ); +}; + +const UsersListWrapper = () => ( + + + + + + + + + + +); + +export { UsersListWrapper }; diff --git a/src/app/modules/process/user-verified/components/header/UserListToolbar.tsx b/src/app/modules/process/user-verified/components/header/UserListToolbar.tsx new file mode 100644 index 0000000..10974f5 --- /dev/null +++ b/src/app/modules/process/user-verified/components/header/UserListToolbar.tsx @@ -0,0 +1,35 @@ +import { KTIcon } from "../../../../../../_digifi/helpers"; +import { useListView } from "../../core/ListViewProvider"; +import { UsersListFilter } from "./UsersListFilter"; + +const UsersListToolbar = () => { + const { setItemIdForUpdate } = useListView(); + const openAddUserModal = () => { + setItemIdForUpdate(null); + }; + + return ( +
+ + + {/* begin::Export */} + {/* */} + {/* end::Export */} + + {/* begin::Add user */} + {/* */} + {/* end::Add user */} +
+ ); +}; + +export { UsersListToolbar }; diff --git a/src/app/modules/process/user-verified/components/header/UsersListFilter.tsx b/src/app/modules/process/user-verified/components/header/UsersListFilter.tsx new file mode 100644 index 0000000..b0fea79 --- /dev/null +++ b/src/app/modules/process/user-verified/components/header/UsersListFilter.tsx @@ -0,0 +1,136 @@ +import { useEffect, useState } from "react"; +import { MenuComponent } from "../../../../../../_digifi/assets/ts/components"; +import { initialQueryState, KTIcon } from "../../../../../../_digifi/helpers"; +import { useQueryRequest } from "../../core/QueryRequestProvider"; +import { useQueryResponse } from "../../core/QueryResponseProvider"; + +const UsersListFilter = () => { + const { updateState } = useQueryRequest(); + const { isLoading } = useQueryResponse(); + const [role, setRole] = useState(); + 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/process/user-verified/components/header/UsersListGrouping.tsx b/src/app/modules/process/user-verified/components/header/UsersListGrouping.tsx new file mode 100644 index 0000000..6c5df5b --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/components/header/UsersListHeader.tsx b/src/app/modules/process/user-verified/components/header/UsersListHeader.tsx new file mode 100644 index 0000000..9d2a3a0 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/components/header/UsersListSearchComponent.tsx b/src/app/modules/process/user-verified/components/header/UsersListSearchComponent.tsx new file mode 100644 index 0000000..e25b9ed --- /dev/null +++ b/src/app/modules/process/user-verified/components/header/UsersListSearchComponent.tsx @@ -0,0 +1,49 @@ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { useEffect, useState } from "react"; +import { + initialQueryState, + KTIcon, + useDebounce, +} from "../../../../../../_digifi/helpers"; +import { useQueryRequest } from "../../core/QueryRequestProvider"; + +const UsersListSearchComponent = () => { + const { updateState } = useQueryRequest(); + const [searchTerm, setSearchTerm] = useState(""); + // 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/process/user-verified/components/loading/UsersListLoading.tsx b/src/app/modules/process/user-verified/components/loading/UsersListLoading.tsx new file mode 100644 index 0000000..2278f87 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/components/pagination/UsersListPagination.tsx b/src/app/modules/process/user-verified/components/pagination/UsersListPagination.tsx new file mode 100644 index 0000000..346131c --- /dev/null +++ b/src/app/modules/process/user-verified/components/pagination/UsersListPagination.tsx @@ -0,0 +1,179 @@ +import clsx from "clsx"; +import { + useQueryResponseLoading, + useQueryResponsePagination, +} from "../../core/QueryResponseProvider"; +import { useQueryRequest } from "../../core/QueryRequestProvider"; +import { PaginationState } from "../../../../../../_digifi/helpers"; +import { useMemo } from "react"; + +const mappedLabel = (label: string): string => { + if (label === "« Previous") { + return "Previous"; + } + + if (label === "Next »") { + return "Next"; + } + + return label; +}; + +const UsersListPagination = () => { + const pagination = useQueryResponsePagination(); + const isLoading = useQueryResponseLoading(); + const { updateState } = useQueryRequest(); + const updatePage = (page: number | undefined | null) => { + if (!page || isLoading || pagination.page === page) { + return; + } + + updateState({ page, items_per_page: pagination.items_per_page || 10 }); + }; + + const PAGINATION_PAGES_COUNT = 5; + const sliceLinks = (pagination?: PaginationState) => { + if (!pagination?.links?.length) { + return []; + } + + const scopedLinks = [...pagination.links]; + + let pageLinks: Array<{ + label: string; + active: boolean; + url: string | null; + page: number | null; + }> = []; + const previousLink: { + label: string; + active: boolean; + url: string | null; + page: number | null; + } = scopedLinks.shift()!; + const nextLink: { + label: string; + active: boolean; + url: string | null; + page: number | null; + } = scopedLinks.pop()!; + + const halfOfPagesCount = Math.floor(PAGINATION_PAGES_COUNT / 2); + + pageLinks.push(previousLink); + + if ( + pagination.page <= Math.round(PAGINATION_PAGES_COUNT / 2) || + scopedLinks.length <= PAGINATION_PAGES_COUNT + ) { + pageLinks = [ + ...pageLinks, + ...scopedLinks.slice(0, PAGINATION_PAGES_COUNT), + ]; + } + + if ( + pagination.page > scopedLinks.length - halfOfPagesCount && + scopedLinks.length > PAGINATION_PAGES_COUNT + ) { + pageLinks = [ + ...pageLinks, + ...scopedLinks.slice( + scopedLinks.length - PAGINATION_PAGES_COUNT, + scopedLinks.length + ), + ]; + } + + if ( + !( + pagination.page <= Math.round(PAGINATION_PAGES_COUNT / 2) || + scopedLinks.length <= PAGINATION_PAGES_COUNT + ) && + !(pagination.page > scopedLinks.length - halfOfPagesCount) + ) { + pageLinks = [ + ...pageLinks, + ...scopedLinks.slice( + pagination.page - 1 - halfOfPagesCount, + pagination.page + halfOfPagesCount + ), + ]; + } + + pageLinks.push(nextLink); + + return pageLinks; + }; + + const paginationLinks = useMemo(() => sliceLinks(pagination), [pagination]); + + return ( + + ); +}; + +export { UsersListPagination }; diff --git a/src/app/modules/process/user-verified/core/ListViewProvider.tsx b/src/app/modules/process/user-verified/core/ListViewProvider.tsx new file mode 100644 index 0000000..d72ff1c --- /dev/null +++ b/src/app/modules/process/user-verified/core/ListViewProvider.tsx @@ -0,0 +1,62 @@ +/* eslint-disable react-refresh/only-export-components */ +import { FC, useState, createContext, useContext, useMemo } from "react"; +import { + ID, + calculatedGroupingIsDisabled, + calculateIsAllDataSelected, + groupingOnSelect, + initialListView, + ListViewContextProps, + groupingOnSelectAll, + WithChildren, +} from "../../../../../_digifi/helpers"; +import { + useQueryResponse, + useQueryResponseData, +} from "./QueryResponseProvider"; + +const ListViewContext = createContext(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/process/user-verified/core/QueryRequestProvider.tsx b/src/app/modules/process/user-verified/core/QueryRequestProvider.tsx new file mode 100644 index 0000000..2fb7824 --- /dev/null +++ b/src/app/modules/process/user-verified/core/QueryRequestProvider.tsx @@ -0,0 +1,29 @@ +/* eslint-disable react-refresh/only-export-components */ +import { FC, useState, createContext, useContext } from "react"; +import { + QueryState, + QueryRequestContextProps, + initialQueryRequest, + WithChildren, +} from "../../../../../_digifi/helpers"; + +const QueryRequestContext = + createContext(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/process/user-verified/core/QueryResponseProvider.tsx b/src/app/modules/process/user-verified/core/QueryResponseProvider.tsx new file mode 100644 index 0000000..28f4519 --- /dev/null +++ b/src/app/modules/process/user-verified/core/QueryResponseProvider.tsx @@ -0,0 +1,87 @@ +/* eslint-disable react-refresh/only-export-components */ +/* eslint-disable react-hooks/exhaustive-deps */ +import { FC, useContext, useState, useEffect, useMemo } from "react"; +import { useQuery } from "react-query"; +import { + createResponseContext, + initialQueryResponse, + initialQueryState, + PaginationState, + QUERIES, + stringifyRequestQuery, + WithChildren, +} from "../../../../../_digifi/helpers"; +import { getVerifiedUsers } from "../../core/_requests"; +import { User } from "../../core/_models"; +import { useQueryRequest } from "./QueryRequestProvider"; + +const QueryResponseContext = createResponseContext(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.VERIFIED_LIST}-${query}`, + () => { + return getVerifiedUsers(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 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, +}; diff --git a/src/app/modules/process/user-verified/edit-loan-modal/EditLoanModal.tsx b/src/app/modules/process/user-verified/edit-loan-modal/EditLoanModal.tsx new file mode 100644 index 0000000..386c111 --- /dev/null +++ b/src/app/modules/process/user-verified/edit-loan-modal/EditLoanModal.tsx @@ -0,0 +1,44 @@ +import {useEffect} from 'react' +import {UserEditModalHeader} from './UserEditModalHeader' +import {UserEditModalFormWrapper} from './UserEditModalFormWrapper' + +const EditLoanModal = () => { + useEffect(() => { + document.body.classList.add('modal-open') + return () => { + document.body.classList.remove('modal-open') + } + }, []) + + return ( + <> + + {/* begin::Modal Backdrop */} +
+ {/* end::Modal Backdrop */} + + ) +} + +export {EditLoanModal} diff --git a/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalForm.tsx b/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalForm.tsx new file mode 100644 index 0000000..22e7401 --- /dev/null +++ b/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalForm.tsx @@ -0,0 +1,434 @@ +import { FC, useState } from "react"; +import * as Yup from "yup"; +import { useFormik } from "formik"; +import { isNotEmpty, toAbsoluteUrl } from "../../../../../_digifi/helpers"; +import { initialUser, User } from "../../core/_models"; +import clsx from "clsx"; +import { useListView } from "../core/ListViewProvider"; +import { UsersListLoading } from "../components/loading/UsersListLoading"; +import { createUser, updateUser } from "../../core/_requests"; +import { useQueryResponse } from "../core/QueryResponseProvider"; + +type Props = { + isUserLoading: boolean; + user: User; +}; + +const editUserSchema = Yup.object().shape({ + email: Yup.string() + .email("Wrong email format") + .min(3, "Minimum 3 symbols") + .max(50, "Maximum 50 symbols") + .required("Email is required"), + name: Yup.string() + .min(3, "Minimum 3 symbols") + .max(50, "Maximum 50 symbols") + .required("Name is required"), +}); + +const UserEditModalForm: FC = ({ 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/process/user-verified/edit-loan-modal/UserEditModalFormWrapper.tsx b/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalFormWrapper.tsx new file mode 100644 index 0000000..61980fa --- /dev/null +++ b/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalFormWrapper.tsx @@ -0,0 +1,46 @@ +import { useQuery } from "react-query"; +import { UserEditModalForm } from "./UserEditModalForm"; +import { isNotEmpty, QUERIES } from "../../../../../_digifi/helpers"; +import { useListView } from "../core/ListViewProvider"; +import { getUserById, getApprovedUsers } from "../../core/_requests"; + +const UserEditModalFormWrapper = () => { + const { itemIdForUpdate, setItemIdForUpdate } = useListView(); + const enabledQuery: boolean = isNotEmpty(itemIdForUpdate); + const { + isLoading, + data: user, + error, + } = useQuery( + `${QUERIES.READY_LIST}-user-${itemIdForUpdate}`, + () => { + // return getUserById(itemIdForUpdate); + return getApprovedUsers(''); + }, + { + cacheTime: 0, + enabled: enabledQuery, + onError: (err) => { + setItemIdForUpdate(undefined); + console.error(err); + }, + } + ); + + if (!itemIdForUpdate) { + return ( + + ); + } + + if (!isLoading && !error && user) { + // return ; + // REMOVE LATER AND ALLOW UP ALONE + let newUser:any = user?.records + return ; + } + + return null; +}; + +export { UserEditModalFormWrapper }; diff --git a/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalHeader.tsx b/src/app/modules/process/user-verified/edit-loan-modal/UserEditModalHeader.tsx new file mode 100644 index 0000000..171b0f1 --- /dev/null +++ b/src/app/modules/process/user-verified/edit-loan-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 */} +

Edit Loan

+ {/* end::Modal title */} + + {/* begin::Close */} +
setItemIdForUpdate(undefined)} + style={{ cursor: "pointer" }} + > + +
+ {/* end::Close */} +
+ ); +}; + +export { UserEditModalHeader }; diff --git a/src/app/modules/process/user-verified/table/UsersTable.tsx b/src/app/modules/process/user-verified/table/UsersTable.tsx new file mode 100644 index 0000000..f1a7150 --- /dev/null +++ b/src/app/modules/process/user-verified/table/UsersTable.tsx @@ -0,0 +1,66 @@ +import { useMemo } from "react"; +import { useTable, ColumnInstance, Row } from "react-table"; +import { CustomHeaderColumn } from "./columns/CustomHeaderColumn"; +import { CustomRow } from "./columns/CustomRow"; +import { + useQueryResponseData, + useQueryResponseLoading, +} from "../core/QueryResponseProvider"; +import { usersColumns } from "./columns/_columns"; +import { User } from "../../core/_models"; +import { UsersListLoading } from "../components/loading/UsersListLoading"; +import { UsersListPagination } from "../components/pagination/UsersListPagination"; +import { KTCardBody } from "../../../../../_digifi/helpers"; + +const UsersTable = () => { + const users = useQueryResponseData(); + // console.log('users44', users) + const isLoading = useQueryResponseLoading(); + const data = useMemo(() => users, [users]); + const columns = useMemo(() => usersColumns, []); + const { getTableProps, getTableBodyProps, headers, rows, prepareRow } = + useTable({ + columns, + data, + }); + + return ( + +
+ + + + {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/process/user-verified/table/columns/AddedCell.tsx b/src/app/modules/process/user-verified/table/columns/AddedCell.tsx new file mode 100644 index 0000000..2e18bad --- /dev/null +++ b/src/app/modules/process/user-verified/table/columns/AddedCell.tsx @@ -0,0 +1,14 @@ +import { FC } from "react"; +import { NewDateTimeFormatter } from "../../../../../../_digifi/lib/NewDateTimeFormatter"; + +type Props = { + added?: string; +}; + +const AddedCell: FC = ({ added }) => ( +
+ {NewDateTimeFormatter(added)} +
+); + +export { AddedCell }; diff --git a/src/app/modules/process/user-verified/table/columns/AgentCell.tsx b/src/app/modules/process/user-verified/table/columns/AgentCell.tsx new file mode 100644 index 0000000..063ade5 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/CustomHeaderColumn.tsx b/src/app/modules/process/user-verified/table/columns/CustomHeaderColumn.tsx new file mode 100644 index 0000000..63eef09 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/CustomRow.tsx b/src/app/modules/process/user-verified/table/columns/CustomRow.tsx new file mode 100644 index 0000000..af64ac0 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/EmployerCell.tsx b/src/app/modules/process/user-verified/table/columns/EmployerCell.tsx new file mode 100644 index 0000000..38ac0f8 --- /dev/null +++ b/src/app/modules/process/user-verified/table/columns/EmployerCell.tsx @@ -0,0 +1,11 @@ +import {FC} from 'react' + +type Props = { + employer_name?: string +} + +const EmployerCell: FC = ({employer_name}) => ( +
{employer_name}
+) + +export {EmployerCell} diff --git a/src/app/modules/process/user-verified/table/columns/UserActionsCell.tsx b/src/app/modules/process/user-verified/table/columns/UserActionsCell.tsx new file mode 100644 index 0000000..c4dd569 --- /dev/null +++ b/src/app/modules/process/user-verified/table/columns/UserActionsCell.tsx @@ -0,0 +1,85 @@ +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 { employersVerify } from "../../../core/_requests"; +import { User } from "../../../core/_models"; + +type Props = { + id: ID; +}; + +const UserActionsCell: FC = ({ id }) => { + const { setItemIdForUpdate } = useListView(); + const { query } = useQueryResponse(); + const queryClient = useQueryClient(); + + // let selectedUser = data?.filter((item:User) => item.uid == id)[0] + + useEffect(() => { + MenuComponent.reinitialization(); + }, []); + + const openEditModal = () => { + setItemIdForUpdate(id); + }; + + const empsVerify = useMutation(() => employersVerify(id), { + // 💡 response of the mutation is passed to onSuccess + onSuccess: () => { + // ✅ update detail view directly + queryClient.invalidateQueries([`${QUERIES.READY_LIST}-${query}`]); + }, + }); + + const resendVerification = async () => { // FUNCTION TO RESEND VERIFICATION + let cont = confirm('Are you sure, you want to send resend verification?') + if(cont){ + await empsVerify.mutateAsync() + } + } + + return ( + <> + + Actions + + + {/* begin::Menu */} +
+ {/* begin::Menu item */} + + {/* end::Menu item */} + + {/* begin::Menu item */} + + {/* end::Menu item */} +
+ {/* end::Menu */} + + ); +}; + +export { UserActionsCell }; diff --git a/src/app/modules/process/user-verified/table/columns/UserCustomHeader.tsx b/src/app/modules/process/user-verified/table/columns/UserCustomHeader.tsx new file mode 100644 index 0000000..9d10d65 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/UserInfoCell.tsx b/src/app/modules/process/user-verified/table/columns/UserInfoCell.tsx new file mode 100644 index 0000000..7a1edf3 --- /dev/null +++ b/src/app/modules/process/user-verified/table/columns/UserInfoCell.tsx @@ -0,0 +1,46 @@ +import clsx from "clsx"; +import { FC } from "react"; +import { toAbsoluteUrl } from "../../../../../../_digifi/helpers"; +import { User } from "../../../core/_models"; + +type Props = { + user: User; +}; + +const UserInfoCell: FC = ({ user }) => ( + +); + +export { UserInfoCell }; diff --git a/src/app/modules/process/user-verified/table/columns/UserLastLoginCell.tsx b/src/app/modules/process/user-verified/table/columns/UserLastLoginCell.tsx new file mode 100644 index 0000000..a8a0ebe --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/UserSelectionCell.tsx b/src/app/modules/process/user-verified/table/columns/UserSelectionCell.tsx new file mode 100644 index 0000000..ee8d295 --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/UserSelectionHeader.tsx b/src/app/modules/process/user-verified/table/columns/UserSelectionHeader.tsx new file mode 100644 index 0000000..1312c0e --- /dev/null +++ b/src/app/modules/process/user-verified/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/process/user-verified/table/columns/_columns.tsx b/src/app/modules/process/user-verified/table/columns/_columns.tsx new file mode 100644 index 0000000..dc90993 --- /dev/null +++ b/src/app/modules/process/user-verified/table/columns/_columns.tsx @@ -0,0 +1,65 @@ +import {Column} from 'react-table' +import {UserInfoCell} from './UserInfoCell' +import { PaymentMonthCell } from './UserLastLoginCell' +import {AgentCell} from './AgentCell' +import {UserActionsCell} from './UserActionsCell' +import {UserSelectionCell} from './UserSelectionCell' +import {UserCustomHeader} from './UserCustomHeader' +import {UserSelectionHeader} from './UserSelectionHeader' +import {User} from '../../../core/_models' +import { AddedCell } from './AddedCell' +import { EmployerCell } from './EmployerCell' + +const usersColumns: ReadonlyArray> = [ + { + Header: (props) => , + id: 'selection', + Cell: ({...props}) => , + }, + { + Header: (props) => , + id: 'firstname', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'employer_name', + Cell: ({...props}) => , + }, + { + Header: (props) => , + accessor: 'loan_amount', + }, + { + Header: (props) => ( + + ), + id: 'payment_month', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'sales_agent', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'added', + Cell: ({...props}) => , + }, + { + Header: (props) => ( + + ), + id: 'actions', + Cell: ({...props}) => , + }, +] + +export {usersColumns} \ No newline at end of file -- 2.34.1