added products endpoint and implement login input fields max length

This commit was merged in pull request #7.
This commit is contained in:
victorAnumudu
2025-09-14 08:16:48 +01:00
parent 82b5ac085d
commit 84f2cdf53f
17 changed files with 538 additions and 375 deletions
+2 -2
View File
@@ -4,10 +4,10 @@ const RouteLinks = {
customerPage: '/customer',
subscriptions: '/subscriptions',
billings: '/billings',
recentSignup: '/recent',
recentSignup: '/recent-signup',
loansPage: '/loans',
transactionsPage: '/transactions',
offers: '/offers',
products: '/products',
usersAdmin: '/users-admin',
transaction_details_page: '/transaction/details',
errorPage: '*',
+7 -7
View File
@@ -10,14 +10,14 @@ 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 UsersAdminPage from "./pages/UsersAdminPage"; // ADMIN USERS PAGE
import RecentSignupPage from "./pages/RecentSignupPage"; // RECENT SIGNUP PAGE
import ProductsPage from './pages/ProductsPage' // PRODUCTS PAGE
import ErrorPage from './pages/ErrorPage';
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';
import UsersAdminPage from "./pages/UsersAdminPage";
import RecentSignupPage from "./pages/RecentSignupPage"; // ERROR PAGE
// const Home = lazy(() => import('./pages/Home'));
@@ -29,15 +29,15 @@ export default function SiteRoutes() {
<Route element={<UserExist />}>
<Route path={RouteLinks.homePage} element={<HomePage />} /> {`*/HOME PAGE*/`}
<Route path={RouteLinks.recentSignup} element={<RecentSignupPage />} /> {`*/HOME PAGE*/`}
<Route path={RouteLinks.recentSignup} element={<RecentSignupPage />} /> {`*/RECENT SIGNUP PAGE*/`}
<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.usersAdmin} element={<UsersAdminPage />} /> {`*/ADMIN USERS 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 path={RouteLinks.usersAdmin} element={<UsersAdminPage />} />
</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() {
@@ -157,7 +157,7 @@ const asideNavLinks = [
{name: 'Billings', status: 1, icon: 'dot', to: RouteLinks.billings},
{
name: 'Configurations', status: 1, icon: 'arrow-right', subLinks: [
{name: 'Product Settings', 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.usersAdmin},
]
},
@@ -2,11 +2,19 @@ import React from 'react'
import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter'
export default function RecentPaymentsBar() {
export default function RecentPaymentsBar({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 Payments[7 days]</p>
<p className='text-base text-white-body font-bold'>Recent Payments [7 days]</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 RecentPaymentsBar() {
<p className='text-sm text-slate-500'>Total</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 +91,7 @@ export default function RecentPaymentsBar() {
</div>
</div>
</div>
}
</div>
</div>
)
@@ -1,4 +1,7 @@
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 Tickets from './Tickets'
@@ -14,6 +17,24 @@ 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 */}
@@ -30,9 +51,9 @@ export default function RightAsideBar() {
</div>
{/* Body */}
{active === 'orders' && <RecentPaymentsBar />}
{active === 'tickets' && <Tickets />}
{active === 'tasks' && <Tasks />}
{active === 'orders' && <RecentPaymentsBar 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>
)
}
+19 -1
View File
@@ -2,11 +2,19 @@ import React from 'react'
import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter'
export default function Tasks() {
export default function Tasks({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 System Checks </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>
)
+78 -60
View File
@@ -2,78 +2,96 @@ import React from 'react'
import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter'
export default function Tickets() {
export default function Tickets({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 Deployments</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'>
<CustomCounter targetNumber={18} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Pending</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>
<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'>
<CustomCounter targetNumber={2} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Offers</p>
:
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'>
<CustomCounter targetNumber={18} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Pending</p>
</div>
<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'>
<CustomCounter targetNumber={2} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Offers</p>
</div>
<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'>
<CustomCounter targetNumber={3} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Created</p>
</div>
<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'>
<CustomCounter targetNumber={1} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Rejected</p>
</div>
</div>
<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'>
<CustomCounter targetNumber={3} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Created</p>
</div>
<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'>
<CustomCounter targetNumber={1} timeInSeconds={1} />
</p>
<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>
<div className='flex flex-col gap-4'>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
{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'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
</div>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
<div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>Project Briefing</p>
<p className='text-sm text-slate-500'>Project Manager</p>
</div>
</div>
</div>
}
</div>
</div>
)
@@ -66,7 +66,6 @@ export default function BillingsCom() {
<option value=''>All</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'>
@@ -1,69 +1,99 @@
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 { getProducts } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString'
// import formatNumber from '../../helpers/formatNumber'
// import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
// import formatNumber from '../../helpers/formatNumber'
// import Avatar from '../../assets/user_avatar.jpg'
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 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.offers, page],
queryFn: () => getOffers({page}),
staleTime: 0,
// placeholderData: keepPreviousData,
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 offers = data?.data?.offers // LOAN CHARGES LIST
const productsData = data?.data?.products // PRODUCTS 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='Products' paths={['Dashboard', 'Products']} />
<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='name'>Name</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 text-right">
Interest Rate
<th scope="col" className="px-2">
Product ID
</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 +102,31 @@ 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" />
<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-left">
<div className="text-base font-semibold">{item?.product_id}</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-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?.management_rate)}</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 +138,7 @@ export default function OffersCom() {
</>
)}
</TablePaginatedWrapper>
</>
}
</div>
</div>
+121 -110
View File
@@ -1,133 +1,144 @@
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 { getRecentSignup } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString'
// import formatNumber from '../../helpers/formatNumber'
// import formatNumber from '../../helpers/formatNumber'
// import Avatar from '../../assets/user_avatar.jpg'
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 RecentSignup() {
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.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 offers = data?.data?.offers // LOAN CHARGES LIST
const recentSignupData = data?.data?.payments // BILLINGS 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='Recent Signup' paths={['Dashboard', 'Recent Acc']} />
<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>
:
<TablePaginatedWrapper data={offers} 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">
Name
</th>
<th scope="col" className="px-2 text-right">
Interest Rate
</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
</th>
{ 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='name'>Name</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 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'>
<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-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>
</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={Avatar} alt="Jese" />
<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>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.interest_rate)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.insurance_rate)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.management_rate)}</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}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
))
:
<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 || 0}`}</span> of <span className="font-semibold text-gray-900 dark:text-white">{isFetching ? '----' : pagination?.total_pages || 0}</span>
</div>
<div className='flex items-center gap-3 md:gap-6'>
<MainBtn
+149 -110
View File
@@ -1,133 +1,172 @@
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 formatNumber from '../../helpers/formatNumber'
// import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
// import formatNumber from '../../helpers/formatNumber'
// import Avatar from '../../assets/user_avatar.jpg'
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 UsersAdmin() {
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_admin, 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?.payments // BILLINGS 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='Users Admin' paths={['Dashboard', 'Admin']} />
<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>
:
<TablePaginatedWrapper data={offers} 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">
Name
</th>
<th scope="col" className="px-2 text-right">
Interest Rate
</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
</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={Avatar} alt="Jese" />
<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>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.interest_rate)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.insurance_rate)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.management_rate)}</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}>
<div className="flex justify-center items-center">
No Record Found
{ 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='name'>Name</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">
Name
</th>
<th scope="col" className="px-2 text-right">
Interest Rate
</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
</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={Avatar} alt="Jese" /> */}
<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>
</td>
</tr>
}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.interest_rate)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.insurance_rate)}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.management_rate)}</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}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</>
}
</div>
</div>
-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 />
)
}
+4
View File
@@ -14,6 +14,10 @@ const queryKeys = {
// new
subscriptions: ['subscriptions'],
billings: ['billings'],
right_sidebar: ['right_sidebar'],
recent_signup: ['recent_signup'],
products: ['products'],
users_admin: ['users_admin'],
}
export default queryKeys
+24
View File
@@ -74,6 +74,30 @@ export const getSubscriptions = (reqData) => {
return getAuxEnd(`/subcriptions`, postData)
}
// FUNCTION TO GET RIGHT SIDEBAR DATA
export const getRightSidebar = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/right-sidebar`, postData)
}
// FUNCTION TO GET RECENT SIGNUP DATA
export const getRecentSignup = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/recent-signup`, postData)
}
// FUNCTION TO GET PRODUCTS DATA
export const getProducts = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/products`, postData)
}
// FUNCTION TO GET USERS DATA
export const getUsers = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/users`, postData)
}