added products endpoint and implement login input fields max length #7

Merged
ameye merged 1 commits from login-input-length into master 2025-09-14 21:17:12 +00:00
17 changed files with 538 additions and 375 deletions
+2 -2
View File
@@ -4,10 +4,10 @@ const RouteLinks = {
customerPage: '/customer', customerPage: '/customer',
subscriptions: '/subscriptions', subscriptions: '/subscriptions',
billings: '/billings', billings: '/billings',
recentSignup: '/recent', recentSignup: '/recent-signup',
loansPage: '/loans', loansPage: '/loans',
transactionsPage: '/transactions', transactionsPage: '/transactions',
offers: '/offers', products: '/products',
usersAdmin: '/users-admin', usersAdmin: '/users-admin',
transaction_details_page: '/transaction/details', transaction_details_page: '/transaction/details',
errorPage: '*', errorPage: '*',
+7 -7
View File
@@ -10,14 +10,14 @@ import HomePage from './pages/HomePage' // Home PAGE
import CustomerPage from './pages/CustomerPage' // REPAYMENTS PAGE import CustomerPage from './pages/CustomerPage' // REPAYMENTS PAGE
import SubscriptionsPage from './pages/SubscriptionsPage' // TRANSACTIONS PAGE import SubscriptionsPage from './pages/SubscriptionsPage' // TRANSACTIONS PAGE
import BillingsPage from './pages/BillingsPage' // LOAN CHARGES 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 LoansPage from './pages/LoansPage' // SELECTED LOANS PAGE
import TransactionDetailsPage from './pages/TransactionDetailsPage' // TRANSACTION DETAILS 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')); // const Home = lazy(() => import('./pages/Home'));
@@ -29,15 +29,15 @@ export default function SiteRoutes() {
<Route element={<UserExist />}> <Route element={<UserExist />}>
<Route path={RouteLinks.homePage} element={<HomePage />} /> {`*/HOME PAGE*/`} <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.subscriptions} element={<SubscriptionsPage />} /> {`*/SUBSCRIPTIONS PAGE*/`}
<Route path={RouteLinks.customerPage} element={<CustomerPage />} /> {`*/CUSTOMER PAGE*/`} <Route path={RouteLinks.customerPage} element={<CustomerPage />} /> {`*/CUSTOMER PAGE*/`}
<Route path={RouteLinks.billings} element={<BillingsPage />} /> {`*/BILLINGS 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.loansPage} element={<LoansPage />} /> {`*/LOANS PAGE*/`}
<Route path={RouteLinks.transaction_details_page} element={<TransactionDetailsPage />} /> {`*/TRANSACTION 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> </Route>
{/* ERROR PAGE */} {/* ERROR PAGE */}
+2 -2
View File
@@ -20,8 +20,8 @@ const initialValues = {
// To get the validation schema // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
username: Yup.string().required("username is required"), 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(12, 'must not exceed 12 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() { export default function LoginCom() {
@@ -157,7 +157,7 @@ const asideNavLinks = [
{name: 'Billings', status: 1, icon: 'dot', to: RouteLinks.billings}, {name: 'Billings', status: 1, icon: 'dot', to: RouteLinks.billings},
{ {
name: 'Configurations', status: 1, icon: 'arrow-right', subLinks: [ 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}, {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 Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter' import CustomCounter from '../../CustomCounter'
export default function RecentPaymentsBar() { export default function RecentPaymentsBar({data, isFetching, isError, error}) {
return ( 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='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'> <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='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'> <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'> <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> <p className='text-sm text-slate-500'>Total</p>
</div> </div>
</div> </div>
}
</div> </div>
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Login</p> <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 flex-col gap-4'>
<div className='flex gap-3 items-center'> <div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'> <div className='px-4 py-2 bg-[#0E172E] rounded-md'>
@@ -74,6 +91,7 @@ export default function RecentPaymentsBar() {
</div> </div>
</div> </div>
</div> </div>
}
</div> </div>
</div> </div>
) )
@@ -1,4 +1,7 @@
import React, { useState } from 'react' 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 Icons from '../../Icons'
//import Orders from './Orders' //import Orders from './Orders'
import Tickets from './Tickets' import Tickets from './Tickets'
@@ -14,6 +17,24 @@ export default function RightAsideBar() {
setActive(lowerStr) 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 ( return (
<div className='w-full h-full flex flex-col gap-8'> <div className='w-full h-full flex flex-col gap-8'>
{/* Menu */} {/* Menu */}
@@ -30,9 +51,9 @@ export default function RightAsideBar() {
</div> </div>
{/* Body */} {/* Body */}
{active === 'orders' && <RecentPaymentsBar />} {active === 'orders' && <RecentPaymentsBar data={recentData} isFetching={isFetching} isError={isError} error={error} />}
{active === 'tickets' && <Tickets />} {active === 'tickets' && <Tickets data={recentData} isFetching={isFetching} isError={isError} error={error} />}
{active === 'tasks' && <Tasks />} {active === 'tasks' && <Tasks data={recentData} isFetching={isFetching} isError={isError} error={error} />}
</div> </div>
) )
} }
+19 -1
View File
@@ -2,11 +2,19 @@ import React from 'react'
import Img from '../../../assets/user_avatar.jpg' import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter' import CustomCounter from '../../CustomCounter'
export default function Tasks() { export default function Tasks({data, isFetching, isError, error}) {
return ( 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='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'> <div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent System Checks </p> <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='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'> <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'> <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> <p className='text-sm text-slate-500'>Created</p>
</div> </div>
</div> </div>
}
</div> </div>
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Best Tasks</p> <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 flex-col gap-4'>
<div className='flex gap-3 items-center'> <div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'> <div className='px-4 py-2 bg-[#0E172E] rounded-md'>
@@ -74,6 +91,7 @@ export default function Tasks() {
</div> </div>
</div> </div>
</div> </div>
}
</div> </div>
</div> </div>
) )
+19 -1
View File
@@ -2,11 +2,19 @@ import React from 'react'
import Img from '../../../assets/user_avatar.jpg' import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter' import CustomCounter from '../../CustomCounter'
export default function Tickets() { export default function Tickets({data, isFetching, isError, error}) {
return ( 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='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'> <div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Deployments</p> <p className='text-base text-white-body font-bold'>Recent Deployments</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='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'> <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'> <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> <p className='text-sm text-slate-500'>Rejected</p>
</div> </div>
</div> </div>
}
</div> </div>
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Tracked Errors</p> <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 flex-col gap-4'>
<div className='flex gap-3 items-center'> <div className='flex gap-3 items-center'>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'> <div className='px-4 py-2 bg-[#0E172E] rounded-md'>
@@ -74,6 +91,7 @@ export default function Tickets() {
</div> </div>
</div> </div>
</div> </div>
}
</div> </div>
</div> </div>
) )
@@ -66,7 +66,6 @@ export default function BillingsCom() {
<option value=''>All</option> <option value=''>All</option>
<option value='option_name'>Option Name</option> <option value='option_name'>Option Name</option>
<option value='member_id'>Member ID</option> <option value='member_id'>Member ID</option>
</select> </select>
</div> </div>
<div className='w-full sm:max-w-48'> <div className='w-full sm:max-w-48'>
@@ -1,69 +1,99 @@
import React, { useState } from 'react' import { useState } from 'react'
import { useQuery } from "@tanstack/react-query"; import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom' import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper' import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons' 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 [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({ const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.offers, page], queryKey: [...queryKeys.products, page, willFilter],
queryFn: () => getOffers({page}), queryFn: () => {
staleTime: 0, const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
// placeholderData: keepPreviousData, const reqData = {
page,
...filterData
}
return getProducts(reqData)
},
staleTime: 0 //0 mins
}) })
const productsData = data?.data?.products // PRODUCTS LIST
const offers = data?.data?.offers // LOAN CHARGES LIST
const pagination = data?.data?.pagination const pagination = data?.data?.pagination
console.log('offers', offers) // console.log('DATA', data?.data)
return ( return (
<div className='w-full flex flex-col gap-8'> <div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Products' paths={['Dashboard', 'Products']} /> <BreadcrumbCom title='Products' paths={['Dashboard', 'Products']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'> <div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ? { isError ?
<> <p className='text-red-500'>{error?.message}</p>
<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}> <>
{/* 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 }) => ( {({ data }) => (
<> <>
<table className="py-2 w-full text-sm"> <table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left"> <thead className="py-2 text-sm text-slate-500 text-left">
<tr> <tr>
<th scope="col" className="px-2 py-2"> <th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
Name Name
</th> </th>
<th scope="col" className="px-2 text-right"> <th scope="col" className="px-2">
Interest Rate Product ID
</th> </th>
<th scope="col" className="px-2 text-right"> <th scope="col" className="px-2 text-right">
Insurance Rate Status
</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> </th>
</tr> </tr>
</thead> </thead>
@@ -72,51 +102,31 @@ export default function OffersCom() {
<tr key={index} className="py-2 border-t border-dashed border-slate-300"> <tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2"> <td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'> <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-left">
<div className="text-base font-semibold">{item?.product_id || ''}</div> <div className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
{/* <div className="font-normal text-gray-500 line-clamp-1">{item?.description}</div> */}
</div> </div>
</div> </div>
</td> </td>
<td className="px-2"> <td className="px-2">
<div className="text-right"> <div className="text-left">
<div className="font-normal text-gray-500">{formatNumber(item?.interest_rate)}</div> <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> </div>
</td> </td>
<td className="px-2"> <td className="px-2">
<div className="text-right"> <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> </div>
</td> </td>
</tr> </tr>
)) ))
: :
<tr className="py-2 border-t border-dashed border-slate-300"> <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"> <div className="flex justify-center items-center">
No Record Found No Record Found
</div> </div>
@@ -128,6 +138,7 @@ export default function OffersCom() {
</> </>
)} )}
</TablePaginatedWrapper> </TablePaginatedWrapper>
</>
} }
</div> </div>
</div> </div>
+73 -62
View File
@@ -1,69 +1,98 @@
import React, { useState } from 'react' import { useState } from 'react'
import { useQuery } from "@tanstack/react-query"; import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom' import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper' import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons' 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() { export default function RecentSignup() {
const [page, setPage] = useState(1) 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({ const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.offers, page], queryKey: [...queryKeys.recent_signup, page, willFilter],
queryFn: () => getOffers({page}), queryFn: () => {
staleTime: 0, const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
// placeholderData: keepPreviousData, const reqData = {
page,
...filterData
}
return getRecentSignup(reqData)
},
staleTime: 0 //0 mins
}) })
const recentSignupData = data?.data?.payments // BILLINGS LIST
const offers = data?.data?.offers // LOAN CHARGES LIST
const pagination = data?.data?.pagination const pagination = data?.data?.pagination
console.log('offers', offers) // console.log('DATA', data?.data)
return ( return (
<div className='w-full flex flex-col gap-8'> <div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Recent Signup' paths={['Dashboard', 'Recent Acc']} /> <BreadcrumbCom title='Recent Signup' paths={['Dashboard', 'Recent Acc']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'> <div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ? { isError ?
<> <p className='text-red-500'>{error?.message}</p>
<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}> <>
{/* 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 }) => ( {({ data }) => (
<> <>
<table className="py-2 w-full text-sm"> <table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left"> <thead className="py-2 text-sm text-slate-500 text-left">
<tr> <tr>
<th scope="col" className="px-2 py-2"> <th scope="col" className="px-2 py-2">
Name Added
</th>
<th scope="col" className="px-2">
Option Name
</th> </th>
<th scope="col" className="px-2 text-right"> <th scope="col" className="px-2 text-right">
Interest Rate Amount
</th> </th>
<th scope="col" className="px-2 text-right"> <th scope="col" className="px-2 text-right">
Insurance Rate Status
</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> </th>
</tr> </tr>
</thead> </thead>
@@ -72,51 +101,32 @@ export default function RecentSignup() {
<tr key={index} className="py-2 border-t border-dashed border-slate-300"> <tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2"> <td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'> <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-left">
<div className="text-base font-semibold">{item?.product_id || ''}</div> <div className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
{/* <div className="font-normal text-gray-500 line-clamp-1">{item?.description}</div> */}
</div> </div>
</div> </div>
</td> </td>
<td className="px-2"> <td className="px-2">
<div className="text-right"> <div className="text-left">
<div className="font-normal text-gray-500">{formatNumber(item?.interest_rate)}</div> <div className="text-base font-semibold">{item?.option_name}</div>
</div> </div>
</td> </td>
<td className="px-2"> <td className="px-2">
<div className="text-right"> <div className="text-right">
<div className="font-normal text-gray-500">{formatNumber(item?.insurance_rate)}</div> <div className="text-base font-semibold">${item?.amount}</div>
{/* <div className="font-normal text-gray-500">{item?.external_url}</div> */}
</div> </div>
</td> </td>
<td className="px-2"> <td className="px-2">
<div className="text-right"> <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> </div>
</td> </td>
</tr> </tr>
)) ))
: :
<tr className="py-2 border-t border-dashed border-slate-300"> <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"> <div className="flex justify-center items-center">
No Record Found No Record Found
</div> </div>
@@ -128,6 +138,7 @@ export default function RecentSignup() {
</> </>
)} )}
</TablePaginatedWrapper> </TablePaginatedWrapper>
</>
} }
</div> </div>
</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='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"> <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>
<div className='flex items-center gap-3 md:gap-6'> <div className='flex items-center gap-3 md:gap-6'>
<MainBtn <MainBtn
+61 -22
View File
@@ -1,44 +1,82 @@
import React, { useState } from 'react' import { useState } from 'react'
import { useQuery } from "@tanstack/react-query"; import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom' import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper' import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons' 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() { export default function UsersAdmin() {
const [page, setPage] = useState(1) 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({ const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.offers, page], queryKey: [...queryKeys.users_admin, page, willFilter],
queryFn: () => getOffers({page}), queryFn: () => {
staleTime: 0, const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
// placeholderData: keepPreviousData, const reqData = {
page,
...filterData
}
return getUsers(reqData)
},
staleTime: 0 //0 mins
}) })
const usersData = data?.data?.payments // BILLINGS LIST
const offers = data?.data?.offers // LOAN CHARGES LIST
const pagination = data?.data?.pagination const pagination = data?.data?.pagination
console.log('offers', offers) // console.log('DATA', data?.data)
return ( return (
<div className='w-full flex flex-col gap-8'> <div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Users Admin' paths={['Dashboard', 'Admin']} /> <BreadcrumbCom title='Users Admin' paths={['Dashboard', 'Admin']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'> <div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ? { isError ?
<> <p className='text-red-500'>{error?.message}</p>
<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}> <>
{/* 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 }) => ( {({ data }) => (
<> <>
<table className="py-2 w-full text-sm"> <table className="py-2 w-full text-sm">
@@ -72,7 +110,7 @@ export default function UsersAdmin() {
<tr key={index} className="py-2 border-t border-dashed border-slate-300"> <tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2"> <td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'> <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" /> {/* <img className="w-10 h-10 rounded-md" src={Avatar} alt="Jese" /> */}
<div className="text-left"> <div className="text-left">
<div className="text-base font-semibold">{item?.product_id || ''}</div> <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="font-normal text-gray-500 line-clamp-1">{item?.description}</div> */}
@@ -128,6 +166,7 @@ export default function UsersAdmin() {
</> </>
)} )}
</TablePaginatedWrapper> </TablePaginatedWrapper>
</>
} }
</div> </div>
</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 // new
subscriptions: ['subscriptions'], subscriptions: ['subscriptions'],
billings: ['billings'], billings: ['billings'],
right_sidebar: ['right_sidebar'],
recent_signup: ['recent_signup'],
products: ['products'],
users_admin: ['users_admin'],
} }
export default queryKeys export default queryKeys
+24
View File
@@ -74,6 +74,30 @@ export const getSubscriptions = (reqData) => {
return getAuxEnd(`/subcriptions`, postData) 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)
}