Compare commits

...

10 Commits

Author SHA1 Message Date
victorAnumudu 1a55cb6a9c added login input fields max length and users page 2025-09-13 16:35:43 +01:00
ameye b303af13f0 Merge branch 'usecontext-fix' of MERMS/MermsFirstOffice into master 2025-09-09 16:48:00 +00:00
victorAnumudu f2671c44a2 fixed use context error 2025-09-09 17:40:41 +01:00
CHIEFSOFT\ameye 59184d38aa Text xhANGES 2025-09-09 12:05:18 -04:00
CHIEFSOFT\ameye 73bc359a77 fix data 2025-09-02 17:03:43 -04:00
CHIEFSOFT\ameye 80fdd6e817 Fix text 2025-09-02 16:52:15 -04:00
ameye 0efa3ffa27 Merge branch 'bug-fix' of MERMS/MermsFirstOffice into master 2025-09-02 20:41:04 +00:00
victorAnumudu 96e6b6853c fixed page error 2025-09-02 20:49:25 +01:00
ameye eddbc7e13f Merge branch 'subscription-table-fix' of MERMS/MermsFirstOffice into master 2025-09-02 19:20:17 +00:00
CHIEFSOFT\ameye e4d6725dea fix names 2025-09-02 13:40:09 -04:00
32 changed files with 566 additions and 130 deletions
+2 -1
View File
@@ -22,7 +22,8 @@
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"build_RE": "react-scripts build",
"build": "GENERATE_SOURCEMAP=false react-scripts build -e .env.production",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
+1 -1
View File
@@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="digiFi global back office systems"
content="MERMS BackOffice Systems"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
+2 -2
View File
@@ -4,7 +4,7 @@ import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
import SiteRoutes from './SiteRoutes';
import LogoutModal from './components/layouts/LogoutModal';
import { generalLayoutContext } from './context/GeneralLayoutContext';
import { GeneralLayoutContext } from './context/GeneralLayoutContext';
import './App.css';
@@ -24,7 +24,7 @@ function App() {
const {pathname} = useLocation()
const {logoutModal, setLogoutModal} = generalLayoutContext()
const {logoutModal, setLogoutModal} = GeneralLayoutContext()
useEffect(()=>{
window.scrollTo(0,0)
+3 -2
View File
@@ -4,10 +4,11 @@ const RouteLinks = {
customerPage: '/customer',
subscriptions: '/subscriptions',
billings: '/billings',
products: '/products',
users: '/users',
recentSignupPage: '/recent-signup',
loansPage: '/loans',
transactionsPage: '/transactions',
offers: '/offers',
transaction_details_page: '/transaction/details',
errorPage: '*',
}
+6 -2
View File
@@ -10,11 +10,13 @@ import HomePage from './pages/HomePage' // Home PAGE
import CustomerPage from './pages/CustomerPage' // REPAYMENTS PAGE
import SubscriptionsPage from './pages/SubscriptionsPage' // TRANSACTIONS PAGE
import BillingsPage from './pages/BillingsPage' // LOAN CHARGES PAGE
import UsersPage from './pages/UsersPage' // USERS PAGE
import ProductsPage from './pages/ProductsPage' // PRODUCTS PAGE
import LoansPage from './pages/LoansPage' // SELECTED LOANS PAGE
import TransactionDetailsPage from './pages/TransactionDetailsPage' // TRANSACTION DETAILS PAGE
import OffersPage from './pages/OffersPage' // LOAN OFFERS PAGE
import ErrorPage from './pages/ErrorPage' // ERROR PAGE
import RecentSignupPage from './pages/RecentSignupPage'
// const Home = lazy(() => import('./pages/Home'));
@@ -29,10 +31,12 @@ export default function SiteRoutes() {
<Route path={RouteLinks.subscriptions} element={<SubscriptionsPage />} /> {`*/SUBSCRIPTIONS PAGE*/`}
<Route path={RouteLinks.customerPage} element={<CustomerPage />} /> {`*/CUSTOMER PAGE*/`}
<Route path={RouteLinks.billings} element={<BillingsPage />} /> {`*/BILLINGS PAGE*/`}
<Route path={RouteLinks.products} element={<ProductsPage />} /> {`*/PRODUCTS PAGE*/`}
<Route path={RouteLinks.users} element={<UsersPage />} /> {`*/USERS PAGE*/`}
<Route path={RouteLinks.recentSignupPage} element={<RecentSignupPage />} /> {`*/RECENT SIGNUP PAGE*/`}
<Route path={RouteLinks.loansPage} element={<LoansPage />} /> {`*/LOANS PAGE*/`}
<Route path={RouteLinks.transaction_details_page} element={<TransactionDetailsPage />} /> {`*/TRANSACTION PAGE*/`}
<Route path={RouteLinks.offers} element={<OffersPage />} /> {`*/LOAN OFFERS PAGE*/`}
</Route>
{/* ERROR PAGE */}
+2 -2
View File
@@ -20,8 +20,8 @@ const initialValues = {
// To get the validation schema
const validationSchema = Yup.object().shape({
username: Yup.string().required("username is required"),
password: Yup.string().required("password is required").min(6, 'must be upto 6 characters').max(12, 'must not exceed 12 characters'),
username: Yup.string().required("username is required").min(6, 'must be upto 6 characters').max(25, 'must not exceed 25 characters'),
password: Yup.string().required("password is required").min(6, 'must be upto 6 characters').max(25, 'must not exceed 25 characters'),
});
export default function LoginCom() {
+2 -2
View File
@@ -1,7 +1,7 @@
import { LuSunDim } from "react-icons/lu";
import { IoMdSunny } from "react-icons/io";
import { generalLayoutContext } from "../../context/GeneralLayoutContext"
import { GeneralLayoutContext } from "../../context/GeneralLayoutContext"
import UserAvatar from '../../assets/user_avatar.jpg'
import HandBurger from "./HandBurger"
@@ -15,7 +15,7 @@ export default function DashboardHeader() {
// let {pathname} = useLocation()
const {theme, handleTheme, setLogoutModal, activeMenu, handleActiveMenu, showAsideDrawer, setShowAsideDrawer} = generalLayoutContext()
const {theme, handleTheme, setLogoutModal, activeMenu, handleActiveMenu, showAsideDrawer, setShowAsideDrawer} = GeneralLayoutContext()
return (
<>
+2 -2
View File
@@ -1,7 +1,7 @@
import { Outlet } from 'react-router-dom'
import DashboardHeader from './DashboardHeader'
import { generalLayoutContext } from '../../context/GeneralLayoutContext'
import { GeneralLayoutContext } from '../../context/GeneralLayoutContext'
import DashboardAside from './aside/DashboardAside'
import RightAsideBar from './rightaside/RightAsideBar'
@@ -9,7 +9,7 @@ export default function DashboardLayout() {
// let {pathname} = useLocation()
const {showAsideDrawer, setShowAsideDrawer} = generalLayoutContext()
const {showAsideDrawer, setShowAsideDrawer} = GeneralLayoutContext()
return (
<div className='w-full flex gap-10 relative m-auto h-screen overflow-x-hidden overflow-y-auto bg-white-body dark:bg-black-body p-8 pt-0 lg:p-10'>
@@ -4,7 +4,6 @@ import { FaArrowRight, FaArrowLeft } from "react-icons/fa6";
import DashboardAside from './aside/DashboardAside'
import DashboardHeader from './DashboardHeader'
import { generalLayoutContext } from '../../context/GeneralLayoutContext'
export default function DashboardLayout() {
const [shrinkAside, setShrinkAside] = useState(false)
+2 -2
View File
@@ -1,10 +1,10 @@
import { Link, useLocation } from "react-router-dom"
import Icons from "../../Icons"
import { generalLayoutContext } from "../../../context/GeneralLayoutContext"
import { GeneralLayoutContext } from "../../../context/GeneralLayoutContext"
export default function AsideLink({name, to, icon}) {
const {shrinkAside, setShowAsideDrawer} = generalLayoutContext()
const {shrinkAside, setShowAsideDrawer} = GeneralLayoutContext()
const {pathname} = useLocation()
@@ -2,12 +2,12 @@ import { useState } from "react";
import { useLocation } from "react-router-dom"
import { FaCaretDown } from "react-icons/fa";
import Icons from "../../Icons";
import { generalLayoutContext } from "../../../context/GeneralLayoutContext";
import { GeneralLayoutContext } from "../../../context/GeneralLayoutContext";
export default function AsideLinkWithSubLinks({name, icon, to, children, isOpen}) {
const {shrinkAside} = generalLayoutContext()
const {shrinkAside} = GeneralLayoutContext()
const {pathname} = useLocation()
@@ -5,7 +5,7 @@ import MainBtn from "../../MainBtn";
import AsideLink from "./AsideLink";
import AsideLinkWithSubLinks from "./AsideLinkWithSubLinks";
import { useSelector } from "react-redux";
import { generalLayoutContext } from "../../../context/GeneralLayoutContext";
import { GeneralLayoutContext } from "../../../context/GeneralLayoutContext";
import { TbLogout2 } from "react-icons/tb";
import UserAvatar from '../../../assets/user_avatar.jpg'
import Icons from "../../Icons";
@@ -14,7 +14,7 @@ export default function DashboardAside() {
const {pathname} = useLocation()
const {setLogoutModal, activeMenu, handleActiveMenu} = generalLayoutContext()
const {setLogoutModal, activeMenu, handleActiveMenu} = GeneralLayoutContext()
const {userDetails} = useSelector((state) => state.userDetails) // GETS LOGGED IN USER ROLE DETAILS
const {role}= userDetails
@@ -138,13 +138,13 @@ export default function DashboardAside() {
const asideNavLinks = [
{name:'Dashboard', status:1, icon: 'dashboard', to: RouteLinks.homePage},
{name:'Deployments', title:'Activities', status:1, icon: 'arrow-right', subLinks: [
{name: 'Active', status:1, icon: 'dot', to: RouteLinks.transactionsPage},
{name: 'Subscriptions', status:1, icon: 'dot', to: RouteLinks.subscriptions},
{name: 'Recent Signup', status:1, icon: 'dot', to: RouteLinks.recentSignupPage},
{name: 'Provisions', status:1, icon: 'dot', to: RouteLinks.subscriptions},
{name: 'Customers', status:1, icon: 'dot', to: RouteLinks.customerPage},
{name: 'Billings', status:1, icon: 'dot', to: RouteLinks.billings},
{name: 'Configurations', status:1, icon: 'arrow-right', subLinks: [
{name: 'Sub. Settings', status:1, icon: 'dot', to: RouteLinks.offers },
{name: 'Users Manager', status:1, icon: 'dot', to: RouteLinks.offers },
{name: 'Product Settings', status:1, icon: 'dot', to: RouteLinks.products },
{name: 'Admin Manager', status:1, icon: 'dot', to: RouteLinks.users },
]
},
],
@@ -6,7 +6,7 @@ import MainBtn from "../../MainBtn";
import AsideLink from "./AsideLink";
import AsideLinkWithSubLinks from "./AsideLinkWithSubLinks";
import { useSelector } from "react-redux";
import { generalLayoutContext } from "../../../context/GeneralLayoutContext";
import { GeneralLayoutContext } from "../../../context/GeneralLayoutContext";
import { TbLogout2 } from "react-icons/tb";
@@ -14,7 +14,7 @@ export default function DashboardAside({shrinkAside=false}) {
const {pathname} = useLocation()
const {setLogoutModal} = generalLayoutContext()
const {setLogoutModal} = GeneralLayoutContext()
const {userDetails} = useSelector((state) => state.userDetails) // GETS LOGGED IN USER ROLE DETAILS
const {role}= userDetails
@@ -6,7 +6,7 @@ import MainBtn from "../../MainBtn";
import AsideLink from "./AsideLink";
import AsideLinkWithSubLinks from "./AsideLinkWithSubLinks";
import { useSelector } from "react-redux";
import { generalLayoutContext } from "../../../context/GeneralLayoutContext";
import { GeneralLayoutContext } from "../../../context/GeneralLayoutContext";
import { TbLogout2 } from "react-icons/tb";
@@ -14,7 +14,7 @@ export default function DashboardAside({shrinkAside=false}) {
const {pathname} = useLocation()
const {setLogoutModal} = generalLayoutContext()
const {setLogoutModal} = GeneralLayoutContext()
const {userDetails} = useSelector((state) => state.userDetails) // GETS LOGGED IN USER ROLE DETAILS
const {role}= userDetails
@@ -2,11 +2,21 @@ import React from 'react'
import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter'
export default function Orders() {
export default function RecentChecks({data, isFetching, isError, error}) {
return (
<div className='h-full p-2 sm:p-4 large:p-8 flex flex-col gap-16 overflow-y-auto aside-scroll-design'>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Checks</p>
{isFetching ?
// <p className='text-base text-white-body font-bold'>Loading...</p>
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='grid grid-cols-2 gap-4 sm:gap-6 large:gap-8'>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
@@ -33,9 +43,18 @@ export default function Orders() {
<p className='text-sm text-slate-500'>Created</p>
</div>
</div>
}
</div>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Login</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='flex flex-col gap-4'>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
@@ -74,6 +93,7 @@ export default function Orders() {
</div>
</div>
</div>
}
</div>
</div>
)
@@ -1,6 +1,9 @@
import React, { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../../services/queryKeys'
import { getRightSidebar } from '../../../services/siteServices'
import Icons from '../../Icons'
import Orders from './Orders'
import RecentChecks from './RecentChecks'
import Tickets from './Tickets'
import Tasks from './Tasks'
@@ -13,6 +16,25 @@ export default function RightAsideBar() {
setActive(lowerStr)
}
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.right_sidebar],
queryFn: () => {
// const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
// const reqData = {
// page,
// ...filterData
// }
return getRightSidebar()
},
// staleTime: 0 //0 mins
})
const recentData = [] // RECENT LIST
// const recentData = data?.data // RECENT LIST
// const pagination = data?.data?.pagination
// console.log('RIGHT', data?.data)
return (
<div className='w-full h-full flex flex-col gap-8'>
{/* Menu */}
@@ -29,9 +51,9 @@ export default function RightAsideBar() {
</div>
{/* Body */}
{active == 'orders' && <Orders />}
{active == 'tickets' && <Tickets />}
{active == 'tasks' && <Tasks />}
{active == 'orders' && <RecentChecks data={recentData} isFetching={isFetching} isError={isError} error={error} />}
{active == 'tickets' && <Tickets data={recentData} isFetching={isFetching} isError={isError} error={error} />}
{active == 'tasks' && <Tasks data={recentData} isFetching={isFetching} isError={isError} error={error} />}
</div>
)
}
@@ -7,6 +7,14 @@ export default function Tasks() {
<div className='h-full p-2 sm:p-4 large:p-8 flex flex-col gap-16 overflow-y-auto aside-scroll-design'>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Support Tasks</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='grid grid-cols-2 gap-4 sm:gap-6 large:gap-8'>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
@@ -33,9 +41,18 @@ export default function Tasks() {
<p className='text-sm text-slate-500'>Created</p>
</div>
</div>
}
</div>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Best Tasks</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='flex flex-col gap-4'>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
@@ -74,6 +91,7 @@ export default function Tasks() {
</div>
</div>
</div>
}
</div>
</div>
)
@@ -7,6 +7,14 @@ export default function Tickets() {
<div className='h-full p-2 sm:p-4 large:p-8 flex flex-col gap-16 overflow-y-auto aside-scroll-design'>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Repayment</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='grid grid-cols-2 gap-4 sm:gap-6 large:gap-8'>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
@@ -33,9 +41,18 @@ export default function Tickets() {
<p className='text-sm text-slate-500'>Rejected</p>
</div>
</div>
}
</div>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Tracked Errors</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='flex flex-col gap-4'>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
@@ -74,6 +91,7 @@ export default function Tickets() {
</div>
</div>
</div>
}
</div>
</div>
)
+5 -5
View File
@@ -64,8 +64,8 @@ export default function BillingsCom() {
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md' onChange={handleFilter}>
<option value=''>All</option>
<option value='transaction_id'>Transaction ID</option>
<option value='account_id'>Account ID</option>
<option value='option_name'>Option Name</option>
<option value='member_id'>Member ID</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
@@ -88,7 +88,7 @@ export default function BillingsCom() {
Option Name
</th>
<th scope="col" className="px-2">
Product ID
Member ID
</th>
<th scope="col" className="px-2 text-right">
Amount
@@ -115,12 +115,12 @@ export default function BillingsCom() {
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.product_id}</div>
<div className="text-base font-semibold">{item?.member_id}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">{item?.amount}</div>
<div className="text-base font-semibold">${item?.amount}</div>
{/* <div className="font-normal text-gray-500">{item?.external_url}</div> */}
</div>
</td>
+154
View File
@@ -0,0 +1,154 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons'
import { getProducts } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
// import formatNumber from '../../helpers/formatNumber'
// import Avatar from '../../assets/user_avatar.jpg'
// import localImgLoader from '../../helpers/localImageLoader'
export default function ProductsCom() {
const [page, setPage] = useState(1)
const [filter, setFilter] = useState({type: '', id: ''})
const [willFilter, setWillFilter] = useState(false)
const handleFilter = ({target:{name, value}}) => {
setFilter(prev => ({...prev, [name]:value}))
}
const handleFilterByParams = () => {
if(filter.type && !filter.id){
return
}else if(!filter.type){
setPage(1)
setWillFilter(prev => !prev)
setFilter({type: '', id: ''})
}else{
setPage(1)
setWillFilter(prev => !prev)
}
}
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.products, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
page,
...filterData
}
return getProducts(reqData)
},
staleTime: 0 //0 mins
})
const productsData = data?.data?.products // PRODUCTS LIST
const pagination = data?.data?.pagination
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Products' paths={['Dashboard', 'Products']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{ isError ?
<p className='text-red-500'>{error?.message}</p>
:
<>
{/* filter section */}
<div className='px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2'>
<Icons name='filter' className='text-3xl' />
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md' onChange={handleFilter}>
<option value=''>All</option>
<option value='product_id'>Product ID</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
<input name='id' value={filter?.id} disabled={!filter.type} className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && 'opacity-30'}`} onChange={handleFilter} />
</div>
<button onClick={handleFilterByParams} disabled={filter.type && !filter.id} className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${(filter.type && !filter.id) && 'opacity-50'}`}>Submit</button>
</div>
{/* end of filter section */}
<TablePaginatedWrapper data={productsData} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
{({ data }) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
Name
</th>
<th scope="col" className="px-2">
Product ID
</th>
{/* <th scope="col" className="px-2 text-right">
Amount
</th> */}
<th scope="col" className="px-2 text-right">
Status
</th>
</tr>
</thead>
<tbody>
{(data && data.length > 0) ? data?.map((item, index) => (
<tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'>
{/* <img className="w-10 h-10 rounded-md" src={localImgLoader(`loan_icons/${item?.type}.png`)} alt="Icon" /> */}
<div className="text-left">
<div className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.name}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.product_id}</div>
</div>
</td>
{/* <td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">${item?.amount}</div>
<div className="font-normal text-gray-500">{item?.external_url}</div>
</div>
</td> */}
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">{item?.status}</div>
</div>
</td>
</tr>
))
:
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</>
}
</div>
</div>
)
}
@@ -0,0 +1,143 @@
import { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons'
import { getRecentSignup } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
import formatNumber from '../../helpers/formatNumber'
import Avatar from '../../assets/user_avatar.jpg'
export default function RecentSignupCom() {
const [page, setPage] = useState(1)
const [filter, setFilter] = useState({type: '', id: ''})
const [willFilter, setWillFilter] = useState(false)
const handleFilter = ({target:{name, value}}) => {
setFilter(prev => ({...prev, [name]:value}))
}
const handleFilterByParams = () => {
if(filter.type && !filter.id){
return
}else if(!filter.type){
setPage(1)
setWillFilter(prev => !prev)
setFilter({type: '', id: ''})
}else{
setPage(1)
setWillFilter(prev => !prev)
}
}
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.recent_signup, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
page,
...filterData
}
return getRecentSignup(reqData)
},
staleTime: 0 //0 mins
})
const recentSignupData = data?.data?.payments // RECENT SIGNUP LIST
const pagination = data?.data?.pagination
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Billings' paths={['Dashboard', 'Billings']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{ isError ?
<p className='text-red-500'>{error?.message}</p>
:
<>
{/* filter section */}
<div className='px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2'>
<Icons name='filter' className='text-3xl' />
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md' onChange={handleFilter}>
<option value=''>All</option>
<option value='username'>Username</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
<input name='id' value={filter?.id} disabled={!filter.type} className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && 'opacity-30'}`} onChange={handleFilter} />
</div>
<button onClick={handleFilterByParams} disabled={filter.type && !filter.id} className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${(filter.type && !filter.id) && 'opacity-50'}`}>Submit</button>
</div>
{/* end of filter section */}
<TablePaginatedWrapper data={recentSignupData} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
{({ data }) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
Option Name
</th>
<th scope="col" className="px-2">
Member ID
</th>
<th scope="col" className="px-2 text-right">
Status
</th>
</tr>
</thead>
<tbody>
{(data && data.length > 0) ? data?.map((item, index) => (
<tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'>
<div className="text-left">
<div className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.option_name}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.member_id}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">{item?.status}</div>
</div>
</td>
</tr>
))
:
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</>
}
</div>
</div>
)
}
@@ -27,7 +27,7 @@ export default function TablePaginatedWrapper({
<div className='w-full flex flex-col lg:flex-row justify-center items-center gap-3 md:gap-6'>
<div className="text-sm text-center lg:text-left font-normal text-gray-500 dark:text-gray-400 block w-full">Showing <span className="font-semibold text-gray-900 dark:text-white">
{isFetching ? '----' : `page ${pagination?.current_page}`}</span> of <span className="font-semibold text-gray-900 dark:text-white">{pagination?.total_pages}</span>
{isFetching ? '----' : `page ${pagination?.current_page}`}</span> of <span className="font-semibold text-gray-900 dark:text-white">{isFetching ? '----' : pagination?.total_pages}</span>
</div>
<div className='flex items-center gap-3 md:gap-6'>
<MainBtn
@@ -51,7 +51,7 @@ export default function SubscriptionsCom() {
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Subscriptions' paths={['Dashboard', 'Subscriptions']} />
<BreadcrumbCom title='Provisions' paths={['Dashboard', 'Provisions']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{ isError ?
@@ -1,69 +1,101 @@
import React, { useState } from 'react'
import { useQuery } from "@tanstack/react-query";
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons'
import { getUsers } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
// import formatNumber from '../../helpers/formatNumber'
// import Avatar from '../../assets/user_avatar.jpg'
// import localImgLoader from '../../helpers/localImageLoader'
import Avatar from '../../assets/user_avatar.jpg'
import queryKeys from '../../services/queryKeys'
import { getOffers } from '../../services/siteServices'
import getDateFromDateString from '../../helpers/GetDateFromDateString';
import formatNumber from '../../helpers/formatNumber';
export default function OffersCom() {
export default function UsersCom() {
const [page, setPage] = useState(1)
const [filter, setFilter] = useState({type: '', id: ''})
const [willFilter, setWillFilter] = useState(false)
const handleFilter = ({target:{name, value}}) => {
setFilter(prev => ({...prev, [name]:value}))
}
const handleFilterByParams = () => {
if(filter.type && !filter.id){
return
}else if(!filter.type){
setPage(1)
setWillFilter(prev => !prev)
setFilter({type: '', id: ''})
}else{
setPage(1)
setWillFilter(prev => !prev)
}
}
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.offers, page],
queryFn: () => getOffers({page}),
staleTime: 0,
// placeholderData: keepPreviousData,
queryKey: [...queryKeys.users, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
page,
...filterData
}
return getUsers(reqData)
},
staleTime: 0 //0 mins
})
const offers = data?.data?.offers // LOAN CHARGES LIST
const usersData = data?.data?.users // USERS LIST
const pagination = data?.data?.pagination
console.log('offers', offers)
// console.log('DATA', data?.data)
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Offers' paths={['Dashboard', 'Offers']} />
<BreadcrumbCom title='Users' paths={['Dashboard', 'Users']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ?
<>
<p className='text-slate-800'>Loading...</p>
</>
: isError ?
<p className='text-red-500'>{error.message}</p>
{ isError ?
<p className='text-red-500'>{error?.message}</p>
:
<TablePaginatedWrapper data={offers} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
<>
{/* filter section */}
<div className='px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2'>
<Icons name='filter' className='text-3xl' />
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md' onChange={handleFilter}>
<option value=''>All</option>
<option value='email'>Email</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
<input name='id' value={filter?.id} disabled={!filter.type} className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && 'opacity-30'}`} onChange={handleFilter} />
</div>
<button onClick={handleFilterByParams} disabled={filter.type && !filter.id} className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${(filter.type && !filter.id) && 'opacity-50'}`}>Submit</button>
</div>
{/* end of filter section */}
<TablePaginatedWrapper data={usersData} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
{({ data }) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
Name
</th>
<th scope="col" className="px-2 text-right">
Interest Rate
<th scope="col" className="px-2">
Email
</th>
{/* <th scope="col" className="px-2 text-right">
Amount
</th> */}
<th scope="col" className="px-2 text-right">
Insurance Rate
</th>
<th scope="col" className="px-2 text-right">
Mgt. Rate
</th>
<th scope="col" className="px-2 text-right">
Max/Min Amount
</th>
<th scope="col" className="px-2 text-right">
Tenor
</th>
<th scope="col" className="px-2 text-right">
Action
Status
</th>
</tr>
</thead>
@@ -72,51 +104,38 @@ export default function OffersCom() {
<tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'>
<img className="w-10 h-10 rounded-md" src={Avatar} alt="Jese image" />
{/* <img className="w-10 h-10 rounded-md" src={localImgLoader(`loan_icons/${item?.type}.png`)} alt="Icon" /> */}
<div className="text-left">
<div className="text-base font-semibold">{item?.product_id || ''}</div>
{/* <div className="font-normal text-gray-500 line-clamp-1">{item?.description}</div> */}
<div className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.interest_rate)}</div>
<div className="text-left">
<div className="text-base font-semibold">{item?.name}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.insurance_rate)}</div>
<div className="text-left">
<div className="text-base font-semibold">{item?.product_id}</div>
</div>
</td>
{/* <td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">${item?.amount}</div>
<div className="font-normal text-gray-500">{item?.external_url}</div>
</div>
</td> */}
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.management_rate)}</div>
<div className="text-base font-semibold">{item?.status}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.maximum_amount)}</div>
<div className="font-normal text-gray-500">{formatNumber(item?.minimum_amount)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{item?.tenor}</div>
</div>
</td>
<td className="px-2 text-right">
<div className='flex items-center justify-end gap-3 md:gap-4'>
<div className='p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md'>
<Icons name='eye' />
</div>
</div>
</td>
</tr>
))
:
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={7}>
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
@@ -128,6 +147,7 @@ export default function OffersCom() {
</>
)}
</TablePaginatedWrapper>
</>
}
</div>
</div>
+6 -6
View File
@@ -1,8 +1,8 @@
import { createContext, useContext, useEffect, useState } from 'react'
const GeneralContextProvider = createContext({})
const GeneralContextProviderInt = createContext({})
export default function GeneralLayoutContext({children}) {
export default function GeneralLayoutContextInt({children}) {
const [theme, setTheme] = useState(null)
@@ -101,13 +101,13 @@ export default function GeneralLayoutContext({children}) {
}
return (
<GeneralContextProvider.Provider value={value}>
<GeneralContextProviderInt.Provider value={value}>
{children}
</GeneralContextProvider.Provider>
</GeneralContextProviderInt.Provider>
)
}
export const generalLayoutContext = () => {
return useContext(GeneralContextProvider)
export const GeneralLayoutContext = () => {
return useContext(GeneralContextProviderInt)
}
+3 -3
View File
@@ -6,7 +6,7 @@ import { Provider } from "react-redux";
import './index.css';
import App from './App';
import store from './store/store.js'
import GeneralLayoutContext from './context/GeneralLayoutContext.jsx';
import GeneralLayoutContextInt from './context/GeneralLayoutContext.jsx';
const root = ReactDOM.createRoot(document.getElementById('root'));
@@ -14,9 +14,9 @@ root.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<GeneralLayoutContext>
<GeneralLayoutContextInt>
<App />
</GeneralLayoutContext>
</GeneralLayoutContextInt>
</Provider>
</BrowserRouter>
</React.StrictMode>
-8
View File
@@ -1,8 +0,0 @@
import React from 'react'
import OffersCom from '../components/offers/OffersCom'
export default function OffersPage() {
return (
<OffersCom />
)
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import ProductsCom from '../components/products/ProductsCom'
export default function ProductsPage() {
return (
<ProductsCom />
)
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import RecentSignupCom from '../components/recent_signup/RecentSignupCom'
export default function RecentSignupPage() {
return (
<RecentSignupCom />
)
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import UsersCom from '../components/users/UsersCom'
export default function UsersPage() {
return (
<UsersCom />
)
}
+4 -1
View File
@@ -5,7 +5,6 @@ const queryKeys = {
transactions: ['transactions'],
repayment_schedule: ['repayment-schedule'],
loan_charges: ['loan-charges'],
offers: ['offers'],
apply_loan: ['apply'],
select_loan: ['select-loan'],
approved_loan: ['approved-loan'],
@@ -14,6 +13,10 @@ const queryKeys = {
// new
subscriptions: ['subscriptions'],
billings: ['billings'],
products: ['products'],
users: ['users'],
right_sidebar: ['right_sidebar'],
recent_signup: ['recent_signup'],
}
export default queryKeys
+23 -6
View File
@@ -74,6 +74,29 @@ export const getSubscriptions = (reqData) => {
return getAuxEnd(`/subcriptions`, postData)
}
// FUNCTION TO GET PRODUCTS LIST TABLE
export const getProducts = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/products`, postData)
}
// FUNCTION TO GET USERS LIST TABLE
export const getUsers = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/users`, postData)
}
// FUNCTION TO GET USERS LIST TABLE
export const getRightSidebar = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/right-sidebar`, postData)
}
// FUNCTION TO GET RECNET SIGNUP LIST TABLE
export const getRecentSignup = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/recent-signup`, postData)
}
@@ -93,12 +116,6 @@ export const getRepayments = (reqData) => {
return getAuxEnd(`/repayments`, postData)
}
// FUNCTION TO GET OFFERS LIST TABLE
export const getOffers = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/offers`, postData)
}
// FUNCTION TO GET REPAYMENT SCHEDULE TABLE
export const getRepaymentSchedule = (reqData) => {
const postData = { ...reqData }