Compare commits

...

19 Commits

Author SHA1 Message Date
victorAnumudu f9e3674ece sidebar menu updated 2024-06-06 17:54:56 +01:00
ameye 3b6b0b580c Merge branch 'employer-list-api' of DigiFi/digifi-bko into master 2024-06-06 16:23:44 +00:00
victorAnumudu 50b12f2da4 added employer list and signatory API 2024-06-06 17:15:12 +01:00
ameye 19198b09ba Merge branch 'employer-list' of DigiFi/digifi-bko into master 2024-06-05 18:20:49 +00:00
victorAnumudu 22894a62d8 employers list added 2024-06-05 18:53:33 +01:00
ameye 5ad9413dc1 Merge branch 'adjust_page_items' of DigiFi/digifi-bko into master 2024-05-17 15:40:44 +00:00
ameye 7449dea29c Merge branch 'nav-bar-bug' of DigiFi/digifi-bko into master 2024-05-17 15:40:39 +00:00
Elias 886c7dd8b3 Back office loan list items:Amount/Filter 2024-05-17 01:09:38 +01:00
victorAnumudu fb6dc18073 nav bar signout hover side menu removed 2024-05-16 15:22:41 +01:00
ameye 7d955381c7 Merge branch 'redundant-pages' of DigiFi/digifi-bko into master 2024-05-16 12:26:08 +00:00
ameye 8df8fa45db Merge branch 'logout_fix' of DigiFi/digifi-bko into master 2024-05-16 12:26:01 +00:00
victorAnumudu 4e60059290 updated pending list action menu 2024-05-15 20:52:53 +01:00
victorAnumudu c6fe8c7d5a updated pending list action menu 2024-05-15 20:52:07 +01:00
victorAnumudu 5eb07b2057 removal of redundant pages 2024-05-15 20:47:29 +01:00
victorAnumudu 3a35a34266 initial commit 2024-05-15 20:22:33 +01:00
Elias de5cb74241 restored logout 2024-05-15 19:00:39 +01:00
ameye c711e000b3 Merge branch 'route-update' of DigiFi/digifi-bko into master 2024-05-15 17:01:19 +00:00
victorAnumudu 0a28d478d8 correct loan route added 2024-05-15 17:41:41 +01:00
ameye 0d9318ddd9 Merge branch 'remove_extra_backoffice_menu' of DigiFi/digifi-bko into master 2024-05-15 08:55:29 +00:00
257 changed files with 6739 additions and 3007 deletions
@@ -1,5 +1,7 @@
const QUERIES = { const QUERIES = {
USERS_LIST: 'users-list', USERS_LIST: 'users-list',
EMPLOYERS_LIST: 'employers-list',
SIGNATORY_LIST: 'signatory-list',
} }
export {QUERIES} export {QUERIES}
+4 -1
View File
@@ -22,8 +22,11 @@ export type SearchState = {
} }
export type Response<T> = { export type Response<T> = {
call_return? : string | any
data?: T data?: T
records?: Array<any> records?: T
salary_sources?: Array<any>
employer_sector?: Array<any>
payload?: { payload?: {
message?: string message?: string
errors?: { errors?: {
+6
View File
@@ -0,0 +1,6 @@
export const formatNumbers = (number: string | undefined): string | null => {
if(!number){
return null
}
return number.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
@@ -60,13 +60,9 @@ const Navbar = () => {
data-kt-menu-attach="parent" data-kt-menu-attach="parent"
data-kt-menu-placement="bottom-end" data-kt-menu-placement="bottom-end"
> >
<img <img src={toAbsoluteUrl('media/avatars/300-3.jpg')} alt="" />
src={toAbsoluteUrl('media/avatars/300-3.jpg')}
alt=""
style={{ cursor: 'auto' }}
/>
</div> </div>
{/* <HeaderUserMenu /> */} <HeaderUserMenu />
</div> </div>
{config.app?.header?.default?.menu?.display && ( {config.app?.header?.default?.menu?.display && (
@@ -17,7 +17,7 @@ const SidebarMenuMain = () => {
{/*<SidebarMenuItem to='/builder' icon='switch' title='Layout Builder' fontIcon='bi-layers' />*/} {/*<SidebarMenuItem to='/builder' icon='switch' title='Layout Builder' fontIcon='bi-layers' />*/}
<div className='menu-item'> <div className='menu-item'>
<div className='menu-content pt-8 pb-2'> <div className='menu-content pt-8 pb-2'>
<span className='menu-section text-muted text-uppercase fs-8 ls-1'>Crafted</span> <span className='menu-section text-muted text-uppercase fs-8 ls-1'>Loan</span>
</div> </div>
</div> </div>
<SidebarMenuItemWithSub <SidebarMenuItemWithSub
@@ -87,7 +87,7 @@ const SidebarMenuMain = () => {
<div className='menu-item'> <div className='menu-item'>
<div className='menu-content pt-8 pb-2'> <div className='menu-content pt-8 pb-2'>
<span className='menu-section text-muted text-uppercase fs-8 ls-1'>Apps</span> <span className='menu-section text-muted text-uppercase fs-8 ls-1'>Tools</span>
</div> </div>
</div> </div>
@@ -103,11 +103,30 @@ const SidebarMenuMain = () => {
</SidebarMenuItemWithSub> */} </SidebarMenuItemWithSub> */}
<SidebarMenuItem <SidebarMenuItem
to='/apps/user-management/users' to='/tools/user-management/users'
icon='abstract-28' icon='abstract-28'
title='User management' title='User management'
fontIcon='bi-layers' fontIcon='bi-layers'
/> />
<div className='menu-item'>
<div className='menu-content pt-8 pb-2'>
<span className='menu-section text-muted text-uppercase fs-8 ls-1'>Employers</span>
</div>
</div>
<SidebarMenuItem
to='employers/employerslist'
icon='abstract-28'
title='List'
fontIcon='bi-layers'
/>
<SidebarMenuItem
to='employers/signatory'
icon='abstract-28'
title='Signatory'
fontIcon='bi-layers'
/>
{/*<div className='menu-item'>*/} {/*<div className='menu-item'>*/}
{/* <a*/} {/* <a*/}
{/* target='_blank'*/} {/* target='_blank'*/}
@@ -1,64 +1,63 @@
import { FC } from 'react';
import {FC} from 'react' import { Link } from 'react-router-dom';
import {Link} from 'react-router-dom' import { useAuth } from '../../../../app/modules/auth';
import {useAuth} from '../../../../app/modules/auth' import { Languages } from './Languages';
import {Languages} from './Languages' import { toAbsoluteUrl } from '../../../helpers';
import {toAbsoluteUrl} from '../../../helpers'
const HeaderUserMenu: FC = () => { const HeaderUserMenu: FC = () => {
const {currentUser, logout} = useAuth() const { currentUser, logout } = useAuth();
return ( return (
<div <div
className='menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg menu-state-primary fw-bold py-4 fs-6 w-275px' className="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg menu-state-primary fw-bold py-4 fs-6 w-275px"
data-kt-menu='true' data-kt-menu="true"
> >
<div className='menu-item px-3'> <div className="menu-item px-3">
<div className='menu-content d-flex align-items-center px-3'> <div className="menu-content d-flex align-items-center px-3">
<div className='symbol symbol-50px me-5'> <div className="symbol symbol-50px me-5">
<img alt='Logo' src={toAbsoluteUrl('media/avatars/300-3.jpg')} /> <img alt="Logo" src={toAbsoluteUrl('media/avatars/300-3.jpg')} />
</div> </div>
<div className='d-flex flex-column'> <div className="d-flex flex-column">
<div className='fw-bolder d-flex align-items-center fs-5'> <div className="fw-bolder d-flex align-items-center fs-5">
{currentUser?.first_name} {currentUser?.first_name} {currentUser?.first_name} {currentUser?.first_name}
{/*<span className='badge badge-light-success fw-bolder fs-8 px-2 py-1 ms-2'>Pro</span>*/} {/*<span className='badge badge-light-success fw-bolder fs-8 px-2 py-1 ms-2'>Pro</span>*/}
</div> </div>
<a href='#' className='fw-bold text-muted text-hover-primary fs-7'> <a href="#" className="fw-bold text-muted text-hover-primary fs-7">
{currentUser?.email} {currentUser?.email}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div className='separator my-2'></div> {/* <div className="separator my-2"></div> */}
<div className='menu-item px-5'> {/* <div className='menu-item px-5'>
<Link to={'/crafted/pages/profile'} className='menu-link px-5'> <Link to={'/crafted/pages/profile'} className='menu-link px-5'>
My Profile My Profile
</Link> </Link>
</div> </div> */}
<div className='menu-item px-5'> {/* <div className='menu-item px-5'>
<a href='#' className='menu-link px-5'> <a href='#' className='menu-link px-5'>
<span className='menu-text'>My Projects</span> <span className='menu-text'>My Projects</span>
<span className='menu-badge'> <span className='menu-badge'>
<span className='badge badge-light-danger badge-circle fw-bolder fs-7'>3</span> <span className='badge badge-light-danger badge-circle fw-bolder fs-7'>3</span>
</span> </span>
</a> </a>
</div> </div> */}
<div {/* <div
className='menu-item px-5' className="menu-item px-5"
data-kt-menu-trigger='hover' data-kt-menu-trigger="hover"
data-kt-menu-placement='left-start' data-kt-menu-placement="left-start"
data-kt-menu-flip='bottom' data-kt-menu-flip="bottom"
> >
<a href='#' className='menu-link px-5'> <a href='#' className='menu-link px-5'>
<span className='menu-title'>My Subscription</span> <span className='menu-title'>My Subscription</span>
<span className='menu-arrow'></span> <span className='menu-arrow'></span>
</a> </a>
<div className='menu-sub menu-sub-dropdown w-175px py-4'> <div className="menu-sub menu-sub-dropdown w-175px py-4">
<div className='menu-item px-3'> <div className='menu-item px-3'>
<a href='#' className='menu-link px-5'> <a href='#' className='menu-link px-5'>
Referrals Referrals
@@ -88,7 +87,7 @@ const HeaderUserMenu: FC = () => {
</a> </a>
</div> </div>
<div className='separator my-2'></div> <div className="separator my-2"></div>
<div className='menu-item px-3'> <div className='menu-item px-3'>
<div className='menu-content px-3'> <div className='menu-content px-3'>
@@ -105,31 +104,31 @@ const HeaderUserMenu: FC = () => {
</div> </div>
</div> </div>
</div> </div>
</div> </div> */}
<div className='menu-item px-5'> {/* <div className='menu-item px-5'>
<a href='#' className='menu-link px-5'> <a href='#' className='menu-link px-5'>
My Statements My Statements
</a> </a>
</div> </div> */}
<div className='separator my-2'></div> <div className="separator my-2"></div>
<Languages /> {/* <Languages /> */}
<div className='menu-item px-5 my-1'> {/* <div className='menu-item px-5 my-1'>
<Link to='/crafted/account/settings' className='menu-link px-5'> <Link to='/crafted/account/settings' className='menu-link px-5'>
Account Settings Account Settings
</Link> </Link>
</div> </div> */}
<div className='menu-item px-5'> <div className="menu-item px-5">
<a onClick={logout} className='menu-link px-5'> <a onClick={logout} className="menu-link px-5">
Sign Out Sign Out
</a> </a>
</div> </div>
</div> </div>
) );
} };
export {HeaderUserMenu} export { HeaderUserMenu };
@@ -5,7 +5,7 @@ import {UsersListWrapper} from './users-list/UsersList'
const usersBreadcrumbs: Array<PageLink> = [ const usersBreadcrumbs: Array<PageLink> = [
{ {
title: 'User Management', title: 'User Management',
path: '/apps/user-management/users', path: '/tools/user-management/users',
isSeparator: false, isSeparator: false,
isActive: false, isActive: false,
}, },
@@ -31,7 +31,7 @@ const UsersPage = () => {
} }
/> />
</Route> </Route>
<Route index element={<Navigate to='/apps/user-management/users' />} /> <Route index element={<Navigate to='/tools/user-management/users' />} />
</Routes> </Routes>
) )
} }
@@ -0,0 +1,49 @@
import {Route, Routes, Outlet, Navigate} from 'react-router-dom'
import {PageLink, PageTitle} from '../../../../_digifi/layout/core'
import {UsersListWrapper} from './users-list/UsersList'
import {UsersListWrapper as SignatoryList} from './signatory-list/UsersList'
const usersBreadcrumbs: Array<PageLink> = [
{
title: 'Employer Management',
path: '/employers/employerslist',
isSeparator: false,
isActive: false,
},
{
title: '',
path: '',
isSeparator: true,
isActive: false,
},
]
const EmployersPage = () => {
return (
<Routes>
<Route element={<Outlet />}>
<Route
path='employerslist'
element={
<>
<PageTitle breadcrumbs={usersBreadcrumbs}>Employers list</PageTitle>
<UsersListWrapper />
</>
}
/>
<Route
path='signatory'
element={
<>
<PageTitle breadcrumbs={usersBreadcrumbs}>Signatory list</PageTitle>
<SignatoryList />
</>
}
/>
</Route>
<Route index element={<Navigate to='/employers/employerslist' />} />
</Routes>
)
}
export default EmployersPage
@@ -1,6 +1,6 @@
import {ListViewProvider, useListView} from './core/ListViewProvider' import {ListViewProvider, useListView} from './core/ListViewProvider'
import {QueryRequestProvider} from './core/QueryRequestProvider' import {QueryRequestProvider} from './core/QueryRequestProvider'
import {QueryResponseProvider} from './core/QueryResponseProvider' import {QueryResponseProvider, useAllResponse} from './core/QueryResponseProvider'
import {UsersListHeader} from './components/header/UsersListHeader' import {UsersListHeader} from './components/header/UsersListHeader'
import {UsersTable} from './table/UsersTable' import {UsersTable} from './table/UsersTable'
import {UserEditModal} from './user-edit-modal/UserEditModal' 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' import { Content } from '../../../../../_digifi/layout/components/content'
const UsersList = () => { const UsersList = () => {
const response = useAllResponse()
console.log('RESPONSE', response)
const {itemIdForUpdate} = useListView() const {itemIdForUpdate} = useListView()
return ( return (
<> <>
@@ -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 (
<>
<div
className='modal fade show d-block'
id='kt_modal_add_user'
role='dialog'
tabIndex={-1}
aria-modal='true'
>
{/* begin::Modal dialog */}
<div className='modal-dialog modal-dialog-centered mw-650px'>
{/* begin::Modal content */}
<div className='modal-content'>
<AddSignatoryModalHeader />
{/* begin::Modal body */}
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
<AddSignatoryModalFormWrapper />
</div>
{/* end::Modal body */}
</div>
{/* end::Modal content */}
</div>
{/* end::Modal dialog */}
</div>
{/* begin::Modal Backdrop */}
<div className='modal-backdrop fade show'></div>
{/* end::Modal Backdrop */}
</>
)
}
export {AddSignatoryModal}
@@ -26,7 +26,7 @@ const editUserSchema = Yup.object().shape({
.required('Name is required'), .required('Name is required'),
}) })
const UserEditModalForm: FC<Props> = ({user, isUserLoading}) => { const AddSignatoryModalForm: FC<Props> = ({user, isUserLoading}) => {
const {setItemIdForUpdate} = useListView() const {setItemIdForUpdate} = useListView()
const {refetch} = useQueryResponse() const {refetch} = useQueryResponse()
@@ -404,4 +404,4 @@ const UserEditModalForm: FC<Props> = ({user, isUserLoading}) => {
) )
} }
export {UserEditModalForm} export {AddSignatoryModalForm}
@@ -1,10 +1,10 @@
import {useQuery} from 'react-query' import {useQuery} from 'react-query'
import {UserEditModalForm} from './UserEditModalForm' import {AddSignatoryModalForm} from './AddSignatoryModalForm'
import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers' import {isNotEmpty, QUERIES} from '../../../../../../_digifi/helpers'
import {useListView} from '../core/ListViewProvider' import {useListView} from '../core/ListViewProvider'
import {getUserById} from '../core/_requests' import {getUserById} from '../core/_requests'
const UserEditModalFormWrapper = () => { const AddSignatoryModalFormWrapper = () => {
const {itemIdForUpdate, setItemIdForUpdate} = useListView() const {itemIdForUpdate, setItemIdForUpdate} = useListView()
const enabledQuery: boolean = isNotEmpty(itemIdForUpdate) const enabledQuery: boolean = isNotEmpty(itemIdForUpdate)
const { const {
@@ -27,14 +27,14 @@ const UserEditModalFormWrapper = () => {
) )
if (!itemIdForUpdate) { if (!itemIdForUpdate) {
return <UserEditModalForm isUserLoading={isLoading} user={{id: undefined}} /> return <AddSignatoryModalForm isUserLoading={isLoading} user={{id: undefined}} />
} }
if (!isLoading && !error && user) { if (!isLoading && !error && user) {
return <UserEditModalForm isUserLoading={isLoading} user={user} /> return <AddSignatoryModalForm isUserLoading={isLoading} user={user} />
} }
return null return null
} }
export {UserEditModalFormWrapper} export {AddSignatoryModalFormWrapper}
@@ -1,7 +1,7 @@
import {KTIcon} from '../../../../../../_digifi/helpers' import {KTIcon} from '../../../../../../_digifi/helpers'
import {useListView} from '../core/ListViewProvider' import {useListView} from '../core/ListViewProvider'
const UserEditModalHeader = () => { const AddSignatoryModalHeader = () => {
const {setItemIdForUpdate} = useListView() const {setItemIdForUpdate} = useListView()
return ( return (
@@ -24,4 +24,4 @@ const UserEditModalHeader = () => {
) )
} }
export {UserEditModalHeader} export {AddSignatoryModalHeader}
@@ -10,9 +10,7 @@ const UsersListToolbar = () => {
return ( return (
<div className='d-flex justify-content-end' data-kt-user-table-toolbar='base'> <div className='d-flex justify-content-end' data-kt-user-table-toolbar='base'>
<UsersListFilter /> {/* begin::Export */}
{/* begin::Export */}
{/* <button type='button' className='btn btn-light-primary me-3'> {/* <button type='button' className='btn btn-light-primary me-3'>
<KTIcon iconName='exit-up' className='fs-2' /> <KTIcon iconName='exit-up' className='fs-2' />
Export Export
@@ -20,11 +18,13 @@ const UsersListToolbar = () => {
{/* end::Export */} {/* end::Export */}
{/* begin::Add user */} {/* begin::Add user */}
{/* <button type='button' className='btn btn-primary' onClick={openAddUserModal}> {/* <button type='button' className='btn btn-primary me-3' onClick={openAddUserModal}>
<KTIcon iconName='plus' className='fs-2' /> <KTIcon iconName='plus' className='fs-2' />
Add User Add New
</button> */} </button> */}
{/* end::Add user */} {/* end::Add user */}
<UsersListFilter />
</div> </div>
) )
} }
@@ -11,7 +11,7 @@ import {
stringifyRequestQuery, stringifyRequestQuery,
WithChildren, WithChildren,
} from '../../../../../../_digifi/helpers' } from '../../../../../../_digifi/helpers'
import {getStartedUsers} from './_requests' import {getSignatoryList} from './_requests'
import {User} from './_models' import {User} from './_models'
import {useQueryRequest} from './QueryRequestProvider' import {useQueryRequest} from './QueryRequestProvider'
@@ -32,9 +32,9 @@ const QueryResponseProvider: FC<WithChildren> = ({children}) => {
refetch, refetch,
data: response, data: response,
} = useQuery( } = useQuery(
`${QUERIES.USERS_LIST}-${query}`, `${QUERIES.SIGNATORY_LIST}-${query}`,
() => { () => {
return getStartedUsers(query) return getSignatoryList(query)
}, },
{cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false} {cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false}
) )
@@ -53,10 +53,17 @@ const useQueryResponseData = () => {
if (!response) { if (!response) {
return [] return []
} }
return response?.records || [] return response?.records || []
} }
const useAllResponse = () => {
const {response} = useQueryResponse()
if (!response) {
return []
}
return response || []
}
const useQueryResponsePagination = () => { const useQueryResponsePagination = () => {
const defaultPaginationState: PaginationState = { const defaultPaginationState: PaginationState = {
links: [], links: [],
@@ -82,4 +89,5 @@ export {
useQueryResponseData, useQueryResponseData,
useQueryResponsePagination, useQueryResponsePagination,
useQueryResponseLoading, useQueryResponseLoading,
useAllResponse
} }
@@ -14,21 +14,13 @@ export type User = {
label: string label: string
state: string state: string
} }
firstname?: string,
lastname?: string
uid?: 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
status?: string
added?: string added?: string
updated?: string updated?: string
email?: string
employer_name?: string
title?: string
phone?: string
} }
export type UsersQueryResponse = Response<Array<User>> export type UsersQueryResponse = Response<Array<User>>
@@ -13,9 +13,9 @@ const NEW_USER_ENDPOINT = import.meta.env.VITE_APP_USER_ENDPOINT
// .get(`${GET_USERS_URL}?${query}`) // .get(`${GET_USERS_URL}?${query}`)
// .then((d: AxiosResponse<UsersQueryResponse>) => d.data); // .then((d: AxiosResponse<UsersQueryResponse>) => d.data);
// }; // };
const getStartedUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE STARTED LOAN APPLICATION const getSignatoryList = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET EMPLOYERS LIST
return axios return axios
.get(`${NEW_USER_ENDPOINT}/loan/started`) .get(`${NEW_USER_ENDPOINT}/employers/signatory`)
.then((d: AxiosResponse<UsersQueryResponse>) => d.data); .then((d: AxiosResponse<UsersQueryResponse>) => d.data);
}; };
@@ -50,7 +50,7 @@ const deleteSelectedUsers = (userIds: Array<ID>): Promise<void> => {
}; };
export { export {
getStartedUsers, getSignatoryList,
deleteUser, deleteUser,
deleteSelectedUsers, deleteSelectedUsers,
getUserById, getUserById,
@@ -0,0 +1,11 @@
import {FC} from 'react'
type Props = {
phone?: string
}
const Phone: FC<Props> = ({phone}) => (
<div className='badge badge-light fw-bolder'>{phone}</div>
)
export {Phone}
@@ -54,6 +54,14 @@ const UserActionsCell: FC<Props> = ({id}) => {
Edit Edit
</a> </a>
</div> </div>
{/* end::Menu item */}
{/* begin::Menu item */}
<div className='menu-item px-3'>
<a className='menu-link px-3' onClick={openEditModal}>
Add Signatory
</a>
</div>
{/* end::Menu item */} {/* end::Menu item */}
{/* begin::Menu item */} {/* begin::Menu item */}
@@ -25,14 +25,14 @@ const UserInfoCell: FC<Props> = ({user}) => (
`text-${user.initials?.state}` `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()}
</div> </div>
)} )}
</a> </a>
</div> </div>
<div className='d-flex flex-column'> <div className='d-flex flex-column'>
<a href='#' className='text-gray-800 text-hover-primary mb-1'> <a href='#' className='text-gray-800 text-hover-primary mb-1'>
{user.firstname} {user.lastname} {user.name}
</a> </a>
<span>{user.email}</span> <span>{user.email}</span>
</div> </div>
@@ -8,6 +8,7 @@ import {UserCustomHeader} from './UserCustomHeader'
import {UserSelectionHeader} from './UserSelectionHeader' import {UserSelectionHeader} from './UserSelectionHeader'
import {User} from '../../core/_models' import {User} from '../../core/_models'
import { AddedCell } from './AddedCell' import { AddedCell } from './AddedCell'
import { Phone } from './Phone'
const usersColumns: ReadonlyArray<Column<User>> = [ const usersColumns: ReadonlyArray<Column<User>> = [
{ {
@@ -17,26 +18,26 @@ const usersColumns: ReadonlyArray<Column<User>> = [
}, },
{ {
Header: (props) => <UserCustomHeader tableProps={props} title='Name' className='min-w-125px' />, Header: (props) => <UserCustomHeader tableProps={props} title='Name' className='min-w-125px' />,
id: 'firstname', id: 'name',
Cell: ({...props}) => <UserInfoCell user={props.data[props.row.index]} />, Cell: ({...props}) => <UserInfoCell user={props.data[props.row.index]} />,
}, },
// {
// Header: (props) => <UserCustomHeader tableProps={props} title='Max Loan' className='min-w-125px' />,
// accessor: 'max_loan',
// },
{ {
Header: (props) => <UserCustomHeader tableProps={props} title='Amount' className='min-w-125px' />, Header: (props) => (
accessor: 'loan_amount', <UserCustomHeader tableProps={props} title='Phone' className='min-w-125px' />
),
id: 'phone',
Cell: ({...props}) => <Phone phone={props.data[props.row.index].phone} />,
}, },
{ {
Header: (props) => ( Header: (props) => (
<UserCustomHeader tableProps={props} title='Payment Terms' className='min-w-125px' /> <UserCustomHeader tableProps={props} title='Employer Name' className='min-w-125px' />
), ),
id: 'payment_month', id: 'employer_name',
Cell: ({...props}) => <PaymentMonthCell payment_month={props.data[props.row.index].payment_month} />, Cell: ({...props}) => <PaymentMonthCell payment_month={props.data[props.row.index].employer_name} />,
},
{
Header: (props) => (
<UserCustomHeader tableProps={props} title='Agent' className='min-w-125px' />
),
id: 'sales_agent',
Cell: ({...props}) => <AgentCell agent={props.data[props.row.index].sales_agent} />,
}, },
{ {
Header: (props) => ( Header: (props) => (
@@ -1,6 +1,6 @@
import {ListViewProvider, useListView} from './core/ListViewProvider' import {ListViewProvider, useListView} from './core/ListViewProvider'
import {QueryRequestProvider} from './core/QueryRequestProvider' import {QueryRequestProvider} from './core/QueryRequestProvider'
import {QueryResponseProvider} from './core/QueryResponseProvider' import {QueryResponseProvider, useAllResponse} from './core/QueryResponseProvider'
import {UsersListHeader} from './components/header/UsersListHeader' import {UsersListHeader} from './components/header/UsersListHeader'
import {UsersTable} from './table/UsersTable' import {UsersTable} from './table/UsersTable'
import {UserEditModal} from './user-edit-modal/UserEditModal' 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' import { Content } from '../../../../../_digifi/layout/components/content'
const UsersList = () => { const UsersList = () => {
const response = useAllResponse()
console.log('RESPONSE', response)
const {itemIdForUpdate} = useListView() const {itemIdForUpdate} = useListView()
return ( return (
<> <>
@@ -10,9 +10,7 @@ const UsersListToolbar = () => {
return ( return (
<div className='d-flex justify-content-end' data-kt-user-table-toolbar='base'> <div className='d-flex justify-content-end' data-kt-user-table-toolbar='base'>
<UsersListFilter /> {/* begin::Export */}
{/* begin::Export */}
{/* <button type='button' className='btn btn-light-primary me-3'> {/* <button type='button' className='btn btn-light-primary me-3'>
<KTIcon iconName='exit-up' className='fs-2' /> <KTIcon iconName='exit-up' className='fs-2' />
Export Export
@@ -20,11 +18,13 @@ const UsersListToolbar = () => {
{/* end::Export */} {/* end::Export */}
{/* begin::Add user */} {/* begin::Add user */}
{/* <button type='button' className='btn btn-primary' onClick={openAddUserModal}> <button type='button' className='btn btn-primary me-3' onClick={openAddUserModal}>
<KTIcon iconName='plus' className='fs-2' /> <KTIcon iconName='plus' className='fs-2' />
Add User Add New
</button> */} </button>
{/* end::Add user */} {/* end::Add user */}
<UsersListFilter />
</div> </div>
) )
} }
@@ -11,7 +11,7 @@ import {
stringifyRequestQuery, stringifyRequestQuery,
WithChildren, WithChildren,
} from '../../../../../../_digifi/helpers' } from '../../../../../../_digifi/helpers'
import {getStartedUsers} from './_requests' import {getEmployersList} from './_requests'
import {User} from './_models' import {User} from './_models'
import {useQueryRequest} from './QueryRequestProvider' import {useQueryRequest} from './QueryRequestProvider'
@@ -32,9 +32,9 @@ const QueryResponseProvider: FC<WithChildren> = ({children}) => {
refetch, refetch,
data: response, data: response,
} = useQuery( } = useQuery(
`${QUERIES.USERS_LIST}-${query}`, `${QUERIES.EMPLOYERS_LIST}-${query}`,
() => { () => {
return getStartedUsers(query) return getEmployersList(query)
}, },
{cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false} {cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false}
) )
@@ -53,10 +53,17 @@ const useQueryResponseData = () => {
if (!response) { if (!response) {
return [] return []
} }
return response?.records || [] return response?.records || []
} }
const useAllResponse = () => {
const {response} = useQueryResponse()
if (!response) {
return []
}
return response || []
}
const useQueryResponsePagination = () => { const useQueryResponsePagination = () => {
const defaultPaginationState: PaginationState = { const defaultPaginationState: PaginationState = {
links: [], links: [],
@@ -82,4 +89,5 @@ export {
useQueryResponseData, useQueryResponseData,
useQueryResponsePagination, useQueryResponsePagination,
useQueryResponseLoading, useQueryResponseLoading,
useAllResponse
} }
@@ -14,21 +14,17 @@ export type User = {
label: string label: string
state: string state: string
} }
firstname?: string,
lastname?: string
uid?: string uid?: string
loan_amount?: string percent_interest?: string
payment_month?: string max_loan?: string
sales_agent?: string tenor?: string
gender?: string | null retirement_age?: string
marital_status?: string
email?: string
address?: string
state?: string
country?: string
status?: string status?: string
sector?: string
salary_source?: string
added?: string added?: string
updated?: string updated?: string
email?: string
} }
export type UsersQueryResponse = Response<Array<User>> export type UsersQueryResponse = Response<Array<User>>
@@ -13,9 +13,9 @@ const NEW_USER_ENDPOINT = import.meta.env.VITE_APP_USER_ENDPOINT
// .get(`${GET_USERS_URL}?${query}`) // .get(`${GET_USERS_URL}?${query}`)
// .then((d: AxiosResponse<UsersQueryResponse>) => d.data); // .then((d: AxiosResponse<UsersQueryResponse>) => d.data);
// }; // };
const getStartedUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE STARTED LOAN APPLICATION const getEmployersList = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET EMPLOYERS LIST
return axios return axios
.get(`${NEW_USER_ENDPOINT}/loan/started`) .get(`${NEW_USER_ENDPOINT}/employers`)
.then((d: AxiosResponse<UsersQueryResponse>) => d.data); .then((d: AxiosResponse<UsersQueryResponse>) => d.data);
}; };
@@ -50,7 +50,7 @@ const deleteSelectedUsers = (userIds: Array<ID>): Promise<void> => {
}; };
export { export {
getStartedUsers, getEmployersList,
deleteUser, deleteUser,
deleteSelectedUsers, deleteSelectedUsers,
getUserById, getUserById,
@@ -0,0 +1,12 @@
import {FC} from 'react'
import { formatNumbers } from '../../../../../../../_digifi/helpers/formatNumbers'
type Props = {
max_loan?: string
}
const MaxLoan: FC<Props> = ({max_loan}) => (
<div className='badge badge-light fw-bolder'>{formatNumbers(max_loan)}</div>
)
export {MaxLoan}
@@ -25,14 +25,14 @@ const UserInfoCell: FC<Props> = ({user}) => (
`text-${user.initials?.state}` `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()}
</div> </div>
)} )}
</a> </a>
</div> </div>
<div className='d-flex flex-column'> <div className='d-flex flex-column'>
<a href='#' className='text-gray-800 text-hover-primary mb-1'> <a href='#' className='text-gray-800 text-hover-primary mb-1'>
{user.firstname} {user.lastname} {user.name}
</a> </a>
<span>{user.email}</span> <span>{user.email}</span>
</div> </div>
@@ -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 { MaxLoan } from './MaxLoan'
const usersColumns: ReadonlyArray<Column<User>> = [
{
Header: (props) => <UserSelectionHeader tableProps={props} />,
id: 'selection',
Cell: ({...props}) => <UserSelectionCell id={props.data[props.row.index].uid} />,
},
{
Header: (props) => <UserCustomHeader tableProps={props} title='Name' className='min-w-125px' />,
id: 'name',
Cell: ({...props}) => <UserInfoCell user={props.data[props.row.index]} />,
},
// {
// Header: (props) => <UserCustomHeader tableProps={props} title='Max Loan' className='min-w-125px' />,
// accessor: 'max_loan',
// },
{
Header: (props) => (
<UserCustomHeader tableProps={props} title='Max Loan' className='min-w-125px' />
),
id: 'max_loan',
Cell: ({...props}) => <MaxLoan max_loan={props.data[props.row.index].max_loan} />,
},
{
Header: (props) => (
<UserCustomHeader tableProps={props} title='Percent Interest' className='min-w-125px' />
),
id: 'percent_interest',
Cell: ({...props}) => <PaymentMonthCell payment_month={props.data[props.row.index].percent_interest} />,
},
{
Header: (props) => (
<UserCustomHeader tableProps={props} title='Retirement Age' className='min-w-125px' />
),
id: 'retirement_age',
Cell: ({...props}) => <AgentCell agent={props.data[props.row.index].retirement_age} />,
},
{
Header: (props) => (
<UserCustomHeader tableProps={props} title='Added' className='min-w-125px' />
),
id: 'added',
Cell: ({...props}) => <AddedCell added={props.data[props.row.index].added} />,
},
{
Header: (props) => (
<UserCustomHeader tableProps={props} title='Actions' className='text-end min-w-100px' />
),
id: 'actions',
Cell: ({...props}) => <UserActionsCell id={props.data[props.row.index].uid} />,
},
]
export {usersColumns}
@@ -1,37 +1,5 @@
import { KTCard } from "../../../../_digifi/helpers" import { UsersListWrapper } from "../user-approved/UsersList";
import { Content } from "../../../../_digifi/layout/components/content"
import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar"
import { UsersListHeader } from "../user-approved/users-list/components/header/UsersListHeader"
import { ListViewProvider, useListView } from "../user-approved/users-list/core/ListViewProvider"
import { QueryRequestProvider } from "../user-approved/users-list/core/QueryRequestProvider"
import { QueryResponseProvider } from "../user-approved/users-list/core/QueryResponseProvider"
import { UsersTable } from "../user-approved/users-list/table/UsersTable"
import { UserEditModal } from "../user-approved/users-list/user-edit-modal/UserEditModal"
const UsersList = () => { const UserApprovedList = () => <UsersListWrapper />;
const {itemIdForUpdate} = useListView()
return (
<>
<KTCard>
<UsersListHeader />
<UsersTable />
</KTCard>
{itemIdForUpdate !== undefined && <UserEditModal />}
</>
)
}
const UserApprovedList = () => ( export { UserApprovedList };
<QueryRequestProvider>
<QueryResponseProvider>
<ListViewProvider>
<ToolbarWrapper />
<Content>
<UsersList />
</Content>
</ListViewProvider>
</QueryResponseProvider>
</QueryRequestProvider>
)
export {UserApprovedList}
@@ -1,37 +1,5 @@
import { KTCard } from "../../../../_digifi/helpers" import { UsersListWrapper } from "../user-pending/UsersList";
import { Content } from "../../../../_digifi/layout/components/content"
import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar"
import { UsersListHeader } from "../user-pending/users-list/components/header/UsersListHeader"
import { ListViewProvider, useListView } from "../user-pending/users-list/core/ListViewProvider"
import { QueryRequestProvider } from "../user-pending/users-list/core/QueryRequestProvider"
import { QueryResponseProvider } from "../user-pending/users-list/core/QueryResponseProvider"
import { UsersTable } from "../user-pending/users-list/table/UsersTable"
import { UserEditModal } from "../user-pending/users-list/user-edit-modal/UserEditModal"
const UsersList = () => { const UserPendingList = () => <UsersListWrapper />;
const {itemIdForUpdate} = useListView()
return (
<>
<KTCard>
<UsersListHeader />
<UsersTable />
</KTCard>
{itemIdForUpdate !== undefined && <UserEditModal />}
</>
)
}
const UserPendingList = () => ( export { UserPendingList };
<QueryRequestProvider>
<QueryResponseProvider>
<ListViewProvider>
<ToolbarWrapper />
<Content>
<UsersList />
</Content>
</ListViewProvider>
</QueryResponseProvider>
</QueryRequestProvider>
)
export {UserPendingList}
@@ -1,37 +1,5 @@
import { KTCard } from "../../../../_digifi/helpers" import { UsersListWrapper } from "../user-ready/UsersList";
import { Content } from "../../../../_digifi/layout/components/content"
import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar"
import { UsersListHeader } from "../user-ready/users-list/components/header/UsersListHeader"
import { ListViewProvider, useListView } from "../user-ready/users-list/core/ListViewProvider"
import { QueryRequestProvider } from "../user-ready/users-list/core/QueryRequestProvider"
import { QueryResponseProvider } from "../user-ready/users-list/core/QueryResponseProvider"
import { UsersTable } from "../user-ready/users-list/table/UsersTable"
import { UserEditModal } from "../user-ready/users-list/user-edit-modal/UserEditModal"
const UsersList = () => { const UserReadyList = () => <UsersListWrapper />;
const {itemIdForUpdate} = useListView()
return (
<>
<KTCard>
<UsersListHeader />
<UsersTable />
</KTCard>
{itemIdForUpdate !== undefined && <UserEditModal />}
</>
)
}
const UserReadyList = () => ( export { UserReadyList };
<QueryRequestProvider>
<QueryResponseProvider>
<ListViewProvider>
<ToolbarWrapper />
<Content>
<UsersList />
</Content>
</ListViewProvider>
</QueryResponseProvider>
</QueryRequestProvider>
)
export {UserReadyList}
@@ -1,37 +1,5 @@
import { KTCard } from "../../../../_digifi/helpers" import { UsersListWrapper } from "../user-rejected/UsersList";
import { Content } from "../../../../_digifi/layout/components/content"
import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar"
import { UsersListHeader } from "../user-rejected/users-list/components/header/UsersListHeader"
import { ListViewProvider, useListView } from "../user-rejected/users-list/core/ListViewProvider"
import { QueryRequestProvider } from "../user-rejected/users-list/core/QueryRequestProvider"
import { QueryResponseProvider } from "../user-rejected/users-list/core/QueryResponseProvider"
import { UsersTable } from "../user-rejected/users-list/table/UsersTable"
import { UserEditModal } from "../user-rejected/users-list/user-edit-modal/UserEditModal"
const UsersList = () => { const UserRejectedList = () => <UsersListWrapper />;
const {itemIdForUpdate} = useListView()
return (
<>
<KTCard>
<UsersListHeader />
<UsersTable />
</KTCard>
{itemIdForUpdate !== undefined && <UserEditModal />}
</>
)
}
const UserRejectedList = () => ( export { UserRejectedList };
<QueryRequestProvider>
<QueryResponseProvider>
<ListViewProvider>
<ToolbarWrapper />
<Content>
<UsersList />
</Content>
</ListViewProvider>
</QueryResponseProvider>
</QueryRequestProvider>
)
export {UserRejectedList}
@@ -1,37 +1,5 @@
import { KTCard } from "../../../../_digifi/helpers" import { UsersListWrapper } from "../user-started/UsersList";
import { Content } from "../../../../_digifi/layout/components/content"
import { ToolbarWrapper } from "../../../../_digifi/layout/components/toolbar"
import { UsersListHeader } from "../user-started/users-list/components/header/UsersListHeader"
import { ListViewProvider, useListView } from "../user-started/users-list/core/ListViewProvider"
import { QueryRequestProvider } from "../user-started/users-list/core/QueryRequestProvider"
import { QueryResponseProvider } from "../user-started/users-list/core/QueryResponseProvider"
import { UsersTable } from "../user-started/users-list/table/UsersTable"
import { UserEditModal } from "../user-started/users-list/user-edit-modal/UserEditModal"
const UsersList = () => { const UserStartedList = () => <UsersListWrapper />;
const {itemIdForUpdate} = useListView()
return (
<>
<KTCard>
<UsersListHeader />
<UsersTable />
</KTCard>
{itemIdForUpdate !== undefined && <UserEditModal />}
</>
)
}
const UserStartedList = () => ( export { UserStartedList };
<QueryRequestProvider>
<QueryResponseProvider>
<ListViewProvider>
<ToolbarWrapper />
<Content>
<UsersList />
</Content>
</ListViewProvider>
</QueryResponseProvider>
</QueryRequestProvider>
)
export {UserStartedList}
@@ -1,4 +1,5 @@
import {ID, Response} from '../../../../../../_digifi/helpers' import { ID, Response } from "../../../../_digifi/helpers"
export type User = { export type User = {
id?: ID id?: ID
name?: string name?: string
@@ -1,5 +1,5 @@
import axios, { AxiosResponse } from "axios"; import axios, { AxiosResponse } from "axios";
import { ID, Response } from "../../../../../../_digifi/helpers"; import { ID, Response } from "../../../../_digifi/helpers"
import { User, UsersQueryResponse } from "./_models"; import { User, UsersQueryResponse } from "./_models";
const API_URL = import.meta.env.VITE_APP_THEME_API_URL; const API_URL = import.meta.env.VITE_APP_THEME_API_URL;
@@ -19,6 +19,30 @@ const getStartedUsers = (query: string): Promise<UsersQueryResponse> => { // FUN
.then((d: AxiosResponse<UsersQueryResponse>) => d.data); .then((d: AxiosResponse<UsersQueryResponse>) => d.data);
}; };
const getRejectedUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE REJECTED LOAN APPLICATION
return axios
.get(`${NEW_USER_ENDPOINT}/loan/rejected`)
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
};
const getPendingUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE PENDING LOAN APPLICATION
return axios
.get(`${NEW_USER_ENDPOINT}/loan/pending`)
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
};
const getReadyUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE READY LOAN APPLICATION
return axios
.get(`${NEW_USER_ENDPOINT}/loan/ready`)
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
};
const getApprovedUsers = (query: string): Promise<UsersQueryResponse> => { // FUNCTION TO GET USERS THAT HAVE APPROVED LOAN APPLICATION
return axios
.get(`${NEW_USER_ENDPOINT}/loan/approved`)
.then((d: AxiosResponse<UsersQueryResponse>) => d.data);
};
const getUserById = (id: ID): Promise<User | undefined> => { const getUserById = (id: ID): Promise<User | undefined> => {
return axios return axios
.get(`${USER_URL}/${id}`) .get(`${USER_URL}/${id}`)
@@ -51,6 +75,10 @@ const deleteSelectedUsers = (userIds: Array<ID>): Promise<void> => {
export { export {
getStartedUsers, getStartedUsers,
getRejectedUsers,
getPendingUsers,
getReadyUsers,
getApprovedUsers,
deleteUser, deleteUser,
deleteSelectedUsers, deleteSelectedUsers,
getUserById, getUserById,
@@ -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 { 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 { itemIdForUpdate } = useListView();
return (
<>
<KTCard>
<UsersListHeader />
<UsersTable />
</KTCard>
{itemIdForUpdate !== undefined && <UserEditModal />}
</>
);
};
const UsersListWrapper = () => (
<QueryRequestProvider>
<QueryResponseProvider>
<ListViewProvider>
<ToolbarWrapper />
<Content>
<UsersList />
</Content>
</ListViewProvider>
</QueryResponseProvider>
</QueryRequestProvider>
);
export { UsersListWrapper };
@@ -1,39 +0,0 @@
import {Route, Routes, Outlet, Navigate} from 'react-router-dom'
import {PageLink, PageTitle} from '../../../../_digifi/layout/core'
import {UsersListWrapper} from './users-list/UsersList'
const usersBreadcrumbs: Array<PageLink> = [
{
title: 'User Management',
path: '/apps/user-management/users',
isSeparator: false,
isActive: false,
},
{
title: '',
path: '',
isSeparator: true,
isActive: false,
},
]
const UsersPage = () => {
return (
<Routes>
<Route element={<Outlet />}>
<Route
path='users'
element={
<>
<PageTitle breadcrumbs={usersBreadcrumbs}>Users list</PageTitle>
<UsersListWrapper />
</>
}
/>
</Route>
<Route index element={<Navigate to='/apps/user-management/users' />} />
</Routes>
)
}
export default UsersPage
@@ -1,15 +1,18 @@
import {KTIcon} from '../../../../../../../_digifi/helpers' import { KTIcon } from "../../../../../../_digifi/helpers";
import {useListView} from '../../core/ListViewProvider' import { useListView } from "../../core/ListViewProvider";
import {UsersListFilter} from './UsersListFilter' import { UsersListFilter } from "./UsersListFilter";
const UsersListToolbar = () => { const UsersListToolbar = () => {
const {setItemIdForUpdate} = useListView() const { setItemIdForUpdate } = useListView();
const openAddUserModal = () => { const openAddUserModal = () => {
setItemIdForUpdate(null) setItemIdForUpdate(null);
} };
return ( return (
<div className='d-flex justify-content-end' data-kt-user-table-toolbar='base'> <div
className="d-flex justify-content-end"
data-kt-user-table-toolbar="base"
>
<UsersListFilter /> <UsersListFilter />
{/* begin::Export */} {/* begin::Export */}
@@ -26,7 +29,7 @@ const UsersListToolbar = () => {
</button> */} </button> */}
{/* end::Add user */} {/* end::Add user */}
</div> </div>
) );
} };
export {UsersListToolbar} export { UsersListToolbar };
@@ -0,0 +1,136 @@
import { useEffect, useState } from "react";
import { MenuComponent } from "../../../../../../_digifi/assets/ts/components";
import { initialQueryState, KTIcon } from "../../../../../../_digifi/helpers";
import { useQueryRequest } from "../../core/QueryRequestProvider";
import { useQueryResponse } from "../../core/QueryResponseProvider";
const UsersListFilter = () => {
const { updateState } = useQueryRequest();
const { isLoading } = useQueryResponse();
const [role, setRole] = useState<string | undefined>();
const [lastLogin, setLastLogin] = useState<string | undefined>();
useEffect(() => {
MenuComponent.reinitialization();
}, []);
const resetData = () => {
updateState({ filter: undefined, ...initialQueryState });
};
const filterData = () => {
updateState({
filter: { role, last_login: lastLogin },
...initialQueryState,
});
};
return (
<>
{/* begin::Filter Button */}
<button
disabled={isLoading}
type="button"
className="btn btn-light-primary me-3"
data-kt-menu-trigger="click"
data-kt-menu-placement="bottom-end"
>
<KTIcon iconName="filter" className="fs-2" />
Filter
</button>
{/* end::Filter Button */}
{/* begin::SubMenu */}
<div
className="menu menu-sub menu-sub-dropdown w-300px w-md-325px"
data-kt-menu="true"
>
{/* begin::Header */}
<div className="px-7 py-5">
<div className="fs-5 text-gray-900 fw-bolder">Filter Options</div>
</div>
{/* end::Header */}
{/* begin::Separator */}
<div className="separator border-gray-200"></div>
{/* end::Separator */}
{/* begin::Content */}
<div className="px-7 py-5" data-kt-user-table-filter="form">
{/* begin::Input group */}
<div className="mb-10">
<label className="form-label fs-6 fw-bold">Role:</label>
<select
className="form-select form-select-solid fw-bolder"
data-kt-select2="true"
data-placeholder="Select option"
data-allow-clear="true"
data-kt-user-table-filter="role"
data-hide-search="true"
onChange={(e) => setRole(e.target.value)}
value={role}
>
<option value=""></option>
<option value="Administrator">Administrator</option>
<option value="Analyst">Analyst</option>
<option value="Developer">Developer</option>
<option value="Support">Support</option>
<option value="Trial">Trial</option>
</select>
</div>
{/* end::Input group */}
{/* begin::Input group */}
<div className="mb-10">
<label className="form-label fs-6 fw-bold">Last login:</label>
<select
className="form-select form-select-solid fw-bolder"
data-kt-select2="true"
data-placeholder="Select option"
data-allow-clear="true"
data-kt-user-table-filter="two-step"
data-hide-search="true"
onChange={(e) => setLastLogin(e.target.value)}
value={lastLogin}
>
<option value=""></option>
<option value="Yesterday">Yesterday</option>
<option value="20 mins ago">20 mins ago</option>
<option value="5 hours ago">5 hours ago</option>
<option value="2 days ago">2 days ago</option>
</select>
</div>
{/* end::Input group */}
{/* begin::Actions */}
<div className="d-flex justify-content-end">
<button
type="button"
disabled={isLoading}
onClick={filterData}
className="btn btn-light btn-active-light-primary fw-bold me-2 px-6"
data-kt-menu-dismiss="true"
data-kt-user-table-filter="reset"
>
Reset
</button>
<button
disabled={isLoading}
type="button"
onClick={resetData}
className="btn btn-primary fw-bold px-6"
data-kt-menu-dismiss="true"
data-kt-user-table-filter="filter"
>
Apply
</button>
</div>
{/* end::Actions */}
</div>
{/* end::Content */}
</div>
{/* end::SubMenu */}
</>
);
};
export { UsersListFilter };
@@ -0,0 +1,38 @@
import { useQueryClient, useMutation } from "react-query";
import { QUERIES } from "../../../../../../_digifi/helpers";
import { useListView } from "../../core/ListViewProvider";
import { useQueryResponse } from "../../core/QueryResponseProvider";
import { deleteSelectedUsers } from "../../../core/_requests";
const UsersListGrouping = () => {
const { selected, clearSelected } = useListView();
const queryClient = useQueryClient();
const { query } = useQueryResponse();
const deleteSelectedItems = useMutation(() => deleteSelectedUsers(selected), {
// 💡 response of the mutation is passed to onSuccess
onSuccess: () => {
// ✅ update detail view directly
queryClient.invalidateQueries([`${QUERIES.USERS_LIST}-${query}`]);
clearSelected();
},
});
return (
<div className="d-flex justify-content-end align-items-center">
<div className="fw-bolder me-5">
<span className="me-2">{selected.length}</span> Selected
</div>
<button
type="button"
className="btn btn-danger"
onClick={async () => await deleteSelectedItems.mutateAsync()}
>
Delete Selected
</button>
</div>
);
};
export { UsersListGrouping };
@@ -0,0 +1,49 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import {
initialQueryState,
KTIcon,
useDebounce,
} from "../../../../../../_digifi/helpers";
import { useQueryRequest } from "../../core/QueryRequestProvider";
const UsersListSearchComponent = () => {
const { updateState } = useQueryRequest();
const [searchTerm, setSearchTerm] = useState<string>("");
// Debounce search term so that it only gives us latest value ...
// ... if searchTerm has not been updated within last 500ms.
// The goal is to only have the API call fire when user stops typing ...
// ... so that we aren't hitting our API rapidly.
const debouncedSearchTerm = useDebounce(searchTerm, 150);
// Effect for API call
useEffect(
() => {
if (debouncedSearchTerm !== undefined && searchTerm !== undefined) {
updateState({ search: debouncedSearchTerm, ...initialQueryState });
}
},
[debouncedSearchTerm] // Only call effect if debounced search term changes
// More details about useDebounce: https://usehooks.com/useDebounce/
);
return (
<div className="card-title">
{/* begin::Search */}
<div className="d-flex align-items-center position-relative my-1">
<KTIcon iconName="magnifier" className="fs-1 position-absolute ms-6" />
<input
type="text"
data-kt-user-table-filter="search"
className="form-control form-control-solid w-250px ps-14"
placeholder="Search user"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
{/* end::Search */}
</div>
);
};
export { UsersListSearchComponent };
@@ -0,0 +1,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 === "&laquo; Previous") {
return "Previous";
}
if (label === "Next &raquo;") {
return "Next";
}
return label;
};
const UsersListPagination = () => {
const pagination = useQueryResponsePagination();
const isLoading = useQueryResponseLoading();
const { updateState } = useQueryRequest();
const updatePage = (page: number | undefined | null) => {
if (!page || isLoading || pagination.page === page) {
return;
}
updateState({ page, items_per_page: pagination.items_per_page || 10 });
};
const PAGINATION_PAGES_COUNT = 5;
const sliceLinks = (pagination?: PaginationState) => {
if (!pagination?.links?.length) {
return [];
}
const scopedLinks = [...pagination.links];
let pageLinks: Array<{
label: string;
active: boolean;
url: string | null;
page: number | null;
}> = [];
const previousLink: {
label: string;
active: boolean;
url: string | null;
page: number | null;
} = scopedLinks.shift()!;
const nextLink: {
label: string;
active: boolean;
url: string | null;
page: number | null;
} = scopedLinks.pop()!;
const halfOfPagesCount = Math.floor(PAGINATION_PAGES_COUNT / 2);
pageLinks.push(previousLink);
if (
pagination.page <= Math.round(PAGINATION_PAGES_COUNT / 2) ||
scopedLinks.length <= PAGINATION_PAGES_COUNT
) {
pageLinks = [
...pageLinks,
...scopedLinks.slice(0, PAGINATION_PAGES_COUNT),
];
}
if (
pagination.page > scopedLinks.length - halfOfPagesCount &&
scopedLinks.length > PAGINATION_PAGES_COUNT
) {
pageLinks = [
...pageLinks,
...scopedLinks.slice(
scopedLinks.length - PAGINATION_PAGES_COUNT,
scopedLinks.length
),
];
}
if (
!(
pagination.page <= Math.round(PAGINATION_PAGES_COUNT / 2) ||
scopedLinks.length <= PAGINATION_PAGES_COUNT
) &&
!(pagination.page > scopedLinks.length - halfOfPagesCount)
) {
pageLinks = [
...pageLinks,
...scopedLinks.slice(
pagination.page - 1 - halfOfPagesCount,
pagination.page + halfOfPagesCount
),
];
}
pageLinks.push(nextLink);
return pageLinks;
};
const paginationLinks = useMemo(() => sliceLinks(pagination), [pagination]);
return (
<div className="row">
<div className="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
<div className="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end">
<div id="kt_table_users_paginate">
<ul className="pagination">
<li
className={clsx("page-item", {
disabled: isLoading || pagination.page === 1,
})}
>
<a
onClick={() => updatePage(1)}
style={{ cursor: "pointer" }}
className="page-link"
>
First
</a>
</li>
{paginationLinks
?.map((link) => {
return { ...link, label: mappedLabel(link.label) };
})
.map((link) => (
<li
key={link.label}
className={clsx("page-item", {
active: pagination.page === link.page,
disabled: isLoading,
previous: link.label === "Previous",
next: link.label === "Next",
})}
>
<a
className={clsx("page-link", {
"page-text":
link.label === "Previous" || link.label === "Next",
"me-5": link.label === "Previous",
})}
onClick={() => updatePage(link.page)}
style={{ cursor: "pointer" }}
>
{mappedLabel(link.label)}
</a>
</li>
))}
<li
className={clsx("page-item", {
disabled:
isLoading ||
pagination.page === (pagination.links?.length || 3) - 2,
})}
>
<a
onClick={() => updatePage((pagination.links?.length || 3) - 2)}
style={{ cursor: "pointer" }}
className="page-link"
>
Last
</a>
</li>
</ul>
</div>
</div>
</div>
);
};
export { UsersListPagination };
@@ -0,0 +1,62 @@
/* eslint-disable react-refresh/only-export-components */
import { FC, useState, createContext, useContext, useMemo } from "react";
import {
ID,
calculatedGroupingIsDisabled,
calculateIsAllDataSelected,
groupingOnSelect,
initialListView,
ListViewContextProps,
groupingOnSelectAll,
WithChildren,
} from "../../../../../_digifi/helpers";
import {
useQueryResponse,
useQueryResponseData,
} from "./QueryResponseProvider";
const ListViewContext = createContext<ListViewContextProps>(initialListView);
const ListViewProvider: FC<WithChildren> = ({ children }) => {
const [selected, setSelected] = useState<Array<ID>>(initialListView.selected);
const [itemIdForUpdate, setItemIdForUpdate] = useState<ID>(
initialListView.itemIdForUpdate
);
const { isLoading } = useQueryResponse();
const data = useQueryResponseData();
const disabled = useMemo(
() => calculatedGroupingIsDisabled(isLoading, data),
[isLoading, data]
);
const isAllSelected = useMemo(
() => calculateIsAllDataSelected(data, selected),
[data, selected]
);
return (
<ListViewContext.Provider
value={{
selected,
itemIdForUpdate,
setItemIdForUpdate,
disabled,
isAllSelected,
onSelect: (id: ID) => {
groupingOnSelect(id, selected, setSelected);
},
onSelectAll: () => {
groupingOnSelectAll(isAllSelected, setSelected, data);
},
clearSelected: () => {
setSelected([]);
},
}}
>
{children}
</ListViewContext.Provider>
);
};
const useListView = () => useContext(ListViewContext);
export { ListViewProvider, useListView };
@@ -0,0 +1,29 @@
/* eslint-disable react-refresh/only-export-components */
import { FC, useState, createContext, useContext } from "react";
import {
QueryState,
QueryRequestContextProps,
initialQueryRequest,
WithChildren,
} from "../../../../../_digifi/helpers";
const QueryRequestContext =
createContext<QueryRequestContextProps>(initialQueryRequest);
const QueryRequestProvider: FC<WithChildren> = ({ children }) => {
const [state, setState] = useState<QueryState>(initialQueryRequest.state);
const updateState = (updates: Partial<QueryState>) => {
const updatedState = { ...state, ...updates } as QueryState;
setState(updatedState);
};
return (
<QueryRequestContext.Provider value={{ state, updateState }}>
{children}
</QueryRequestContext.Provider>
);
};
const useQueryRequest = () => useContext(QueryRequestContext);
export { QueryRequestProvider, useQueryRequest };
@@ -0,0 +1,87 @@
/* eslint-disable react-refresh/only-export-components */
/* eslint-disable react-hooks/exhaustive-deps */
import { FC, useContext, useState, useEffect, useMemo } from "react";
import { useQuery } from "react-query";
import {
createResponseContext,
initialQueryResponse,
initialQueryState,
PaginationState,
QUERIES,
stringifyRequestQuery,
WithChildren,
} from "../../../../../_digifi/helpers";
import { getApprovedUsers } from "../../core/_requests";
import { User } from "../../core/_models";
import { useQueryRequest } from "./QueryRequestProvider";
const QueryResponseContext = createResponseContext<User>(initialQueryResponse);
const QueryResponseProvider: FC<WithChildren> = ({ children }) => {
const { state } = useQueryRequest();
const [query, setQuery] = useState<string>(stringifyRequestQuery(state));
const updatedQuery = useMemo(() => stringifyRequestQuery(state), [state]);
useEffect(() => {
if (query !== updatedQuery) {
setQuery(updatedQuery);
}
}, [updatedQuery]);
const {
isFetching,
refetch,
data: response,
} = useQuery(
`${QUERIES.USERS_LIST}-${query}`,
() => {
return getApprovedUsers(query);
},
{ cacheTime: 0, keepPreviousData: true, refetchOnWindowFocus: false }
);
return (
<QueryResponseContext.Provider
value={{ isLoading: isFetching, refetch, response, query }}
>
{children}
</QueryResponseContext.Provider>
);
};
const useQueryResponse = () => useContext(QueryResponseContext);
const useQueryResponseData = () => {
const { response } = useQueryResponse();
if (!response) {
return [];
}
return response?.records || [];
};
const useQueryResponsePagination = () => {
const defaultPaginationState: PaginationState = {
links: [],
...initialQueryState,
};
const { response } = useQueryResponse();
if (!response || !response.payload || !response.payload.pagination) {
return defaultPaginationState;
}
return response.payload.pagination;
};
const useQueryResponseLoading = (): boolean => {
const { isLoading } = useQueryResponse();
return isLoading;
};
export {
QueryResponseProvider,
useQueryResponse,
useQueryResponseData,
useQueryResponsePagination,
useQueryResponseLoading,
};
@@ -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('users', users)
const isLoading = useQueryResponseLoading();
const data = useMemo(() => users, [users]);
const columns = useMemo(() => usersColumns, []);
const { getTableProps, getTableBodyProps, headers, rows, prepareRow } =
useTable({
columns,
data,
});
return (
<KTCardBody className="py-4">
<div className="table-responsive">
<table
id="kt_table_users"
className="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer"
{...getTableProps()}
>
<thead>
<tr className="text-start text-muted fw-bolder fs-7 text-uppercase gs-0">
{headers.map((column: ColumnInstance<User>) => (
<CustomHeaderColumn key={column.id} column={column} />
))}
</tr>
</thead>
<tbody className="text-gray-600 fw-bold" {...getTableBodyProps()}>
{rows.length > 0 ? (
rows.map((row: Row<User>, i) => {
prepareRow(row);
return <CustomRow row={row} key={`row-${i}-${row.id}`} />;
})
) : (
<tr>
<td colSpan={7}>
<div className="d-flex text-center w-100 align-content-center justify-content-center">
No matching records found
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
<UsersListPagination />
{isLoading && <UsersListLoading />}
</KTCardBody>
);
};
export { UsersTable };
@@ -0,0 +1,14 @@
import { FC } from "react";
import { NewDateTimeFormatter } from "../../../../../../_digifi/lib/NewDateTimeFormatter";
type Props = {
added?: string;
};
const AddedCell: FC<Props> = ({ added }) => (
<div className="badge badge-light fw-bolder">
{NewDateTimeFormatter(added)}
</div>
);
export { AddedCell };
@@ -1,6 +1,6 @@
import {FC} from 'react' import {FC} from 'react'
import {ColumnInstance} from 'react-table' import {ColumnInstance} from 'react-table'
import {User} from '../../core/_models' import {User} from '../../../core/_models'
type Props = { type Props = {
column: ColumnInstance<User> column: ColumnInstance<User>
@@ -1,7 +1,7 @@
import clsx from 'clsx' import clsx from 'clsx'
import {FC} from 'react' import {FC} from 'react'
import {Row} from 'react-table' import {Row} from 'react-table'
import {User} from '../../core/_models' import {User} from '../../../core/_models'
type Props = { type Props = {
row: Row<User> row: Row<User>
@@ -0,0 +1,75 @@
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<Props> = ({ 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 (
<>
<a
href="#"
className="btn btn-light btn-active-light-primary btn-sm"
data-kt-menu-trigger="click"
data-kt-menu-placement="bottom-end"
>
Actions
<KTIcon iconName="down" className="fs-5 m-0" />
</a>
{/* begin::Menu */}
<div
className="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold fs-7 w-125px py-4"
data-kt-menu="true"
>
{/* begin::Menu item */}
<div className="menu-item px-3">
<a className="menu-link px-3" onClick={openEditModal}>
Edit
</a>
</div>
{/* end::Menu item */}
{/* begin::Menu item */}
<div className="menu-item px-3">
<a
className="menu-link px-3"
data-kt-users-table-filter="delete_row"
onClick={async () => await deleteItem.mutateAsync()}
>
Delete
</a>
</div>
{/* end::Menu item */}
</div>
{/* end::Menu */}
</>
);
};
export { UserActionsCell };
@@ -0,0 +1,61 @@
import clsx from "clsx";
import { FC, PropsWithChildren, useMemo } from "react";
import { HeaderProps } from "react-table";
import { initialQueryState } from "../../../../../../_digifi/helpers";
import { useQueryRequest } from "../../core/QueryRequestProvider";
import { User } from "../../../core/_models";
type Props = {
className?: string;
title?: string;
tableProps: PropsWithChildren<HeaderProps<User>>;
};
const UserCustomHeader: FC<Props> = ({ className, title, tableProps }) => {
const id = tableProps.column.id;
const { state, updateState } = useQueryRequest();
const isSelectedForSorting = useMemo(() => {
return state.sort && state.sort === id;
}, [state, id]);
const order: "asc" | "desc" | undefined = useMemo(() => state.order, [state]);
const sortColumn = () => {
// avoid sorting for these columns
if (id === "actions" || id === "selection") {
return;
}
if (!isSelectedForSorting) {
// enable sort asc
updateState({ sort: id, order: "asc", ...initialQueryState });
return;
}
if (isSelectedForSorting && order !== undefined) {
if (order === "asc") {
// enable sort desc
updateState({ sort: id, order: "desc", ...initialQueryState });
return;
}
// disable sort
updateState({ sort: undefined, order: undefined, ...initialQueryState });
}
};
return (
<th
{...tableProps.column.getHeaderProps()}
className={clsx(
className,
isSelectedForSorting && order !== undefined && `table-sort-${order}`
)}
style={{ cursor: "pointer" }}
onClick={sortColumn}
>
{title}
</th>
);
};
export { UserCustomHeader };
@@ -0,0 +1,46 @@
import clsx from "clsx";
import { FC } from "react";
import { toAbsoluteUrl } from "../../../../../../_digifi/helpers";
import { User } from "../../../core/_models";
type Props = {
user: User;
};
const UserInfoCell: FC<Props> = ({ user }) => (
<div className="d-flex align-items-center">
{/* begin:: Avatar */}
<div className="symbol symbol-circle symbol-50px overflow-hidden me-3">
<a href="#">
{user.avatar ? (
<div className="symbol-label">
<img
src={toAbsoluteUrl(`media/${user.avatar}`)}
alt={user.name}
className="w-100"
/>
</div>
) : (
<div
className={clsx(
"symbol-label fs-3",
`bg-light-${user.initials?.state}`,
`text-${user.initials?.state}`
)}
>
{user.firstname?.substring(0, 1).toUpperCase()}{" "}
{user.lastname?.substring(0, 1).toUpperCase()}
</div>
)}
</a>
</div>
<div className="d-flex flex-column">
<a href="#" className="text-gray-800 text-hover-primary mb-1">
{user.firstname} {user.lastname}
</a>
<span>{user.email}</span>
</div>
</div>
);
export { UserInfoCell };

Some files were not shown because too many files have changed in this diff Show More