Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a55cb6a9c | |||
| b303af13f0 | |||
| f2671c44a2 | |||
| 59184d38aa | |||
| 73bc359a77 | |||
| 80fdd6e817 | |||
| 0efa3ffa27 | |||
| 96e6b6853c | |||
| eddbc7e13f | |||
| 691d9e72fe | |||
| e4d6725dea | |||
| 607d64c709 | |||
| 882f781b75 | |||
| e9d30d6169 | |||
| baf96da5eb | |||
| 88182bdbdf |
+17
-1
@@ -3,8 +3,22 @@
|
||||
# FROM node:alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
# Build args
|
||||
ARG NODE_ENV
|
||||
|
||||
ENV NODE_VERSION 14.19.0
|
||||
ENV NODE_ENV=$NODE_ENV
|
||||
|
||||
# install nginx
|
||||
RUN apk update
|
||||
RUN apk add nginx
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY nginx.conf ./
|
||||
|
||||
COPY run.sh ./
|
||||
|
||||
COPY package.json .
|
||||
|
||||
RUN npm install
|
||||
@@ -15,5 +29,7 @@ COPY . .
|
||||
|
||||
|
||||
# CMD [ "npm", "run", "preview" ]
|
||||
CMD [ "npm", "run", "start" ,"--", "--host"]
|
||||
##CMD [ "npm", "run", "start" ,"--", "--host"]
|
||||
|
||||
CMD /bin/sh ./run.sh
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- NODE_ENV=development
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./:/app
|
||||
- '/app/node_modules'
|
||||
- ./run.sh:/app/run.sh
|
||||
ports:
|
||||
- 8092:5173
|
||||
expose:
|
||||
@@ -21,6 +24,7 @@ services:
|
||||
- socket.mermsemr.com:10.10.33.15
|
||||
environment:
|
||||
- PORT=${DIGIFI_PORT}
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
tty: true
|
||||
stdin_open: true
|
||||
volumes:
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
server {
|
||||
gzip on;
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
root /usr/src/app/build;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
location /nginx_status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+110
-14
@@ -10,10 +10,13 @@
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"cra-template": "1.2.0",
|
||||
"formik": "^2.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-redux": "^9.2.0",
|
||||
@@ -3255,6 +3258,62 @@
|
||||
"string.prototype.matchall": "^4.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.draggable.js": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz",
|
||||
"integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.filter.js": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz",
|
||||
"integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.js": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
|
||||
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Fuzzyma"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.resize.js": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz",
|
||||
"integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4",
|
||||
"@svgdotjs/svg.select.js": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.select.js": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz",
|
||||
"integrity": "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz",
|
||||
@@ -4301,6 +4360,12 @@
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
@@ -4562,6 +4627,20 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-4.7.0.tgz",
|
||||
"integrity": "sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.draggable.js": "^3.0.4",
|
||||
"@svgdotjs/svg.filter.js": "^3.0.8",
|
||||
"@svgdotjs/svg.js": "^3.2.4",
|
||||
"@svgdotjs/svg.resize.js": "^2.0.2",
|
||||
"@svgdotjs/svg.select.js": "^4.0.1",
|
||||
"@yr/monotone-cubic-spline": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@@ -5949,6 +6028,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/countup.js": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.9.0.tgz",
|
||||
"integrity": "sha512-llqrvyXztRFPp6+i8jx25phHWcVWhrHO4Nlt0uAOSKHB8778zzQswa4MU3qKBvkXfJKftRYFJuVHez67lyKdHg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cra-template": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cra-template/-/cra-template-1.2.0.tgz",
|
||||
@@ -13776,6 +13861,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-apexcharts": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.7.0.tgz",
|
||||
"integrity": "sha512-03oScKJyNLRf0Oe+ihJxFZliBQM9vW3UWwomVn4YVRTN1jsIR58dLWt0v1sb8RwJVHDMbeHiKQueM0KGpn7nOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"apexcharts": ">=4.0.0",
|
||||
"react": ">=0.13"
|
||||
}
|
||||
},
|
||||
"node_modules/react-app-polyfill": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz",
|
||||
@@ -13799,6 +13897,18 @@
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-countup": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz",
|
||||
"integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"countup.js": "^2.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||
@@ -16472,20 +16582,6 @@
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||
|
||||
+2
-1
@@ -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
@@ -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" />
|
||||
<!--
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
set -x
|
||||
|
||||
export NODE_ENV="${NODE_ENV:-development}"
|
||||
|
||||
if [ $NODE_ENV = "development" ]; then
|
||||
# this runs webpack-dev-server with hot reloading
|
||||
echo "Development build"
|
||||
npm install --legacy-peer-deps
|
||||
npm run start -- --host
|
||||
# npm run build
|
||||
elif [ $NODE_ENV = "qa" -o $NODE_ENV = "test" ]; then
|
||||
echo "QA build"
|
||||
export NODE_ENV="test"
|
||||
npm install --legacy-peer-deps
|
||||
npm run start -- --host
|
||||
else
|
||||
# build the app and serve it via nginx
|
||||
echo "Production build"
|
||||
npm install --legacy-peer-deps
|
||||
npm run build
|
||||
# Testing sometimes
|
||||
# npm run start
|
||||
nginx -g 'daemon off;' -c /usr/src/app/nginx.conf
|
||||
nginx -c /usr/src/app/nginx.conf
|
||||
fi
|
||||
|
||||
+2
-2
@@ -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
@@ -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
@@ -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 */}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,28 +138,15 @@ 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: 'Product Settings', status:1, icon: 'dot', to: RouteLinks.offers },
|
||||
{name: 'Manage 2', 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 },
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
// {name:'Product 2', title:'Product 2', status:1, icon: 'product', subLinks: [
|
||||
// {name: 'Applications', status:1, icon: 'dot', to: ''},
|
||||
// ]
|
||||
// },
|
||||
// {name:'Product 3', title:'Product 3', status:1, icon: 'product', subLinks: [
|
||||
// {name: 'Applications', status:1, icon: 'dot', to: ''},
|
||||
// {name: 'Configuration', status:1, icon: 'dot', to: ''},
|
||||
// ]
|
||||
// },
|
||||
// {name:'Administration', title:'Admin', status:1, icon: 'people', subLinks: [
|
||||
// {name: 'Users', status:1, icon: 'dot', to: RouteLinks.usersPage},
|
||||
// ]
|
||||
// },
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
+21
-1
@@ -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>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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 { getBillings } from '../../services/siteServices'
|
||||
import getDateFromDateString from '../../helpers/GetDateFromDateString';
|
||||
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
|
||||
import formatNumber from '../../helpers/formatNumber'
|
||||
import Avatar from '../../assets/user_avatar.jpg'
|
||||
|
||||
@@ -12,10 +14,9 @@ import Avatar from '../../assets/user_avatar.jpg'
|
||||
export default function BillingsCom() {
|
||||
|
||||
const [page, setPage] = useState(1)
|
||||
const [allBillings, setAllBillings] = useState({loading:true, error:'', data:{}})
|
||||
|
||||
const [willFilter, setWillFilter] = useState(false)
|
||||
const [filter, setFilter] = useState({type: '', id: ''})
|
||||
const [willFilter, setWillFilter] = useState(false)
|
||||
|
||||
const handleFilter = ({target:{name, value}}) => {
|
||||
setFilter(prev => ({...prev, [name]:value}))
|
||||
}
|
||||
@@ -33,25 +34,20 @@ export default function BillingsCom() {
|
||||
}
|
||||
}
|
||||
|
||||
const billings = allBillings?.data?.loan_charges // LOAN CHARGES LIST
|
||||
const pagination = allBillings?.data?.pagination
|
||||
const isFetching = allBillings?.loading
|
||||
const isError = allBillings?.error
|
||||
|
||||
useEffect(()=>{
|
||||
setAllBillings(prev => ({...prev, loading:true}))
|
||||
const payload = filter?.type ? {[filter?.type]: filter.id} : {}
|
||||
getBillings({...payload, page}).then(res => {
|
||||
if(res?.status != 200){
|
||||
setAllBillings(prev => ({...prev, error:'Opps, an error occurred', loading:false}))
|
||||
return
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: [...queryKeys.billings, page, willFilter],
|
||||
queryFn: () => {
|
||||
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
|
||||
const reqData = {
|
||||
page,
|
||||
...filterData
|
||||
}
|
||||
setAllBillings({loading:false, error:'', data:res?.data})
|
||||
}).catch(err => {
|
||||
setAllBillings({loading:false, error:'error occurred', data:{}})
|
||||
console.log('ERR', err)
|
||||
})
|
||||
},[page, willFilter])
|
||||
return getBillings(reqData)
|
||||
},
|
||||
staleTime: 0 //0 mins
|
||||
})
|
||||
const billingData = data?.data?.payments // BILLINGS LIST
|
||||
const pagination = data?.data?.pagination
|
||||
|
||||
return (
|
||||
<div className='w-full flex flex-col gap-8'>
|
||||
@@ -59,7 +55,7 @@ export default function BillingsCom() {
|
||||
|
||||
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
|
||||
{ isError ?
|
||||
<p className='text-red-500'>{allBillings?.error}</p>
|
||||
<p className='text-red-500'>{error?.message}</p>
|
||||
:
|
||||
<>
|
||||
{/* filter section */}
|
||||
@@ -68,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'>
|
||||
@@ -79,26 +75,26 @@ export default function BillingsCom() {
|
||||
</div>
|
||||
{/* end of filter section */}
|
||||
|
||||
<TablePaginatedWrapper data={billings} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
|
||||
<TablePaginatedWrapper data={billingData} 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
|
||||
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">
|
||||
Loan
|
||||
</th> */}
|
||||
<th scope="col" className="px-2 text-right">
|
||||
Amount
|
||||
</th>
|
||||
<th scope="col" className="px-2 text-right">
|
||||
Added
|
||||
</th>
|
||||
<th scope="col" className="px-2 text-right">
|
||||
Action
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -107,31 +103,32 @@ export default function BillingsCom() {
|
||||
<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" />
|
||||
<div className="text-left">
|
||||
<div className="text-base font-semibold">{item?.transaction_id || ''}</div>
|
||||
<div className="font-normal text-gray-500 line-clamp-1">{item?.code}</div>
|
||||
<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">{formatNumber(item?.initial_loan_amount)}</div> */}
|
||||
<div className="font-normal text-gray-500">{formatNumber(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>
|
||||
<td className="px-2">
|
||||
<div className="text-right">
|
||||
<div className="font-normal text-gray-500">{getDateFromDateString(item?.created_at)}</div>
|
||||
<div className="text-base font-semibold">{item?.status}</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>
|
||||
))
|
||||
:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ import Icons from '../Icons'
|
||||
|
||||
import Avatar from '../../assets/user_avatar.jpg'
|
||||
import queryKeys from '../../services/queryKeys'
|
||||
import { getLoanCharges } from '../../services/siteServices'
|
||||
//import { getLoanCharges } from '../../services/siteServices'
|
||||
import getDateFromDateString from '../../helpers/GetDateFromDateString';
|
||||
import formatNumber from '../../helpers/formatNumber';
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function LoanChargeDetails({transactionID}) {
|
||||
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: [...queryKeys.loan_charges, page],
|
||||
queryFn: () => getLoanCharges({transaction_id: transactionID, page}),
|
||||
// queryFn: () => getLoanCharges({transaction_id: transactionID, page}),
|
||||
staleTime: 0,
|
||||
// placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import Icons from '../Icons'
|
||||
|
||||
import queryKeys from '../../services/queryKeys'
|
||||
import { getTransactions } from '../../services/siteServices'
|
||||
//import { getTransactions } from '../../services/siteServices'
|
||||
import getDateFromDateString from '../../helpers/GetDateFromDateString';
|
||||
import getTimeFromDateString from '../../helpers/GetTimeFromDateString';
|
||||
import localImgLoader from '../../helpers/localImageLoader';
|
||||
@@ -15,7 +15,7 @@ export default function TransactionDetails({transactionID}) {
|
||||
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: [...queryKeys.transactions, page],
|
||||
queryFn: () => getTransactions({transaction_id: transactionID, page}),
|
||||
//queryFn: () => getTransactions({transaction_id: transactionID, page}),
|
||||
staleTime: 0,
|
||||
// placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
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 { getSubcriptions } from '../../services/siteServices'
|
||||
import getDateFromDateString from '../../helpers/GetDateFromDateString';
|
||||
import getTimeFromDateString from '../../helpers/GetTimeFromDateString';
|
||||
import { getSubscriptions } from '../../services/siteServices'
|
||||
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
|
||||
import localImgLoader from '../../helpers/localImageLoader';
|
||||
import RouteLinks from '../../RouteLinks';
|
||||
|
||||
export default function SubscriptionsCom() {
|
||||
|
||||
const [page, setPage] = useState(1)
|
||||
const [allSubcriptions, setAllSubcriptions] = useState({loading:true, error:'', data:{}})
|
||||
|
||||
const [willFilter, setWillFilter] = useState(false)
|
||||
const [filter, setFilter] = useState({type: '', id: ''})
|
||||
const [willFilter, setWillFilter] = useState(false)
|
||||
|
||||
const handleFilter = ({target:{name, value}}) => {
|
||||
setFilter(prev => ({...prev, [name]:value}))
|
||||
}
|
||||
@@ -34,33 +34,28 @@ export default function SubscriptionsCom() {
|
||||
}
|
||||
}
|
||||
|
||||
const subcriptions = allSubcriptions?.data?.transactions // TRANSACTIONS LIST
|
||||
const pagination = allSubcriptions?.data?.pagination
|
||||
const isFetching = allSubcriptions?.loading
|
||||
const isError = allSubcriptions?.error
|
||||
|
||||
useEffect(()=>{
|
||||
setAllSubcriptions(prev => ({...prev, loading:true}))
|
||||
const payload = filter?.type ? {[filter?.type]: filter.id} : {}
|
||||
getSubcriptions({...payload, page}).then(res => {
|
||||
if(res?.status != 200){
|
||||
setAllSubcriptions(prev => ({...prev, error:'Opps, an error occurred', loading:false}))
|
||||
return
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: [...queryKeys.subscriptions, page, willFilter],
|
||||
queryFn: () => {
|
||||
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
|
||||
const reqData = {
|
||||
page,
|
||||
...filterData
|
||||
}
|
||||
setAllSubcriptions({loading:false, error:'', data:res?.data})
|
||||
}).catch(err => {
|
||||
setAllSubcriptions({loading:false, error:'error occurred', data:{}})
|
||||
console.log('ERR', err)
|
||||
})
|
||||
},[page, willFilter])
|
||||
return getSubscriptions(reqData)
|
||||
},
|
||||
staleTime: 0 //0 mins
|
||||
})
|
||||
const subscriptionData = data?.data?.subscriptions // SUBSCRIPTION LIST
|
||||
const pagination = data?.data?.pagination
|
||||
|
||||
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 ?
|
||||
<p className='text-red-500'>{allSubcriptions?.error}</p>
|
||||
<p className='text-red-500'>{error?.message}</p>
|
||||
:
|
||||
<>
|
||||
{/* filter section */}
|
||||
@@ -69,8 +64,8 @@ export default function SubscriptionsCom() {
|
||||
<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='member_id'>Member ID</option>
|
||||
<option value='product_id'>Product ID</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className='w-full sm:max-w-48'>
|
||||
@@ -80,23 +75,23 @@ export default function SubscriptionsCom() {
|
||||
</div>
|
||||
{/* end of filter section */}
|
||||
|
||||
<TablePaginatedWrapper data={subcriptions} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
|
||||
<TablePaginatedWrapper data={subscriptionData} 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">
|
||||
Request
|
||||
Added
|
||||
</th>
|
||||
<th scope="col" className="px-2">
|
||||
Account
|
||||
Product
|
||||
</th>
|
||||
<th scope="col" className="px-2">
|
||||
Activity
|
||||
URL
|
||||
</th>
|
||||
<th scope="col" className="px-2 text-right">
|
||||
Action
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -105,36 +100,28 @@ export default function SubscriptionsCom() {
|
||||
<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" />
|
||||
{/* <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?.transaction_id}</div>
|
||||
<div className="font-normal text-gray-500">{getDateFromDateString(item?.created_at)} {getTimeFromDateString(item?.created_at)}</div>
|
||||
<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?.account_id}</div>
|
||||
<div className="font-normal text-gray-500">{item?.type}</div>
|
||||
<div className="text-base font-semibold">{item?.product_id}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2">
|
||||
<div className="text-left">
|
||||
<div className="text-base font-semibold">{item?.internal_url}</div>
|
||||
<div className="font-normal text-gray-500">{item?.external_url}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2">
|
||||
<div className="text-left">
|
||||
<div className="font-normal text-gray-500">50%</div>
|
||||
<div className="relative h-[6px] w-full bg-white-body dark:bg-black-body rounded-full overflow-hidden">
|
||||
<div className={`absolute left-0 h-full w-1/2 bg-emerald-600`}></div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-base font-semibold">{item?.status}</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'>
|
||||
<Link to={RouteLinks.transaction_details_page} state={{transactionID: item?.transaction_id}}>
|
||||
<Icons name='eye' />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
:
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
function getDateTimeFromDateString(dateString) {
|
||||
const date = new Date(dateString);
|
||||
|
||||
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
const dayName = days[date.getDay()];
|
||||
const monthName = months[date.getMonth()];
|
||||
const day = date.getDate();
|
||||
const year = date.getFullYear();
|
||||
|
||||
// Add ordinal suffix
|
||||
function getOrdinal(n) {
|
||||
if (n > 3 && n < 21) return "th"; // 4-20 are all "th"
|
||||
switch (n % 10) {
|
||||
case 1: return "st";
|
||||
case 2: return "nd";
|
||||
case 3: return "rd";
|
||||
default: return "th";
|
||||
}
|
||||
}
|
||||
|
||||
// Format time in 12hr with AM/PM
|
||||
let hours = date.getHours();
|
||||
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
const ampm = hours >= 12 ? "PM" : "AM";
|
||||
hours = hours % 12 || 12;
|
||||
|
||||
return `${dayName}, ${monthName} ${day}${getOrdinal(day)} ${year} ${hours}:${minutes}${ampm}`;
|
||||
}
|
||||
|
||||
export default getDateTimeFromDateString
|
||||
+3
-3
@@ -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>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
import OffersCom from '../components/offers/OffersCom'
|
||||
|
||||
export default function OffersPage() {
|
||||
return (
|
||||
<OffersCom />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import ProductsCom from '../components/products/ProductsCom'
|
||||
|
||||
export default function ProductsPage() {
|
||||
return (
|
||||
<ProductsCom />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import RecentSignupCom from '../components/recent_signup/RecentSignupCom'
|
||||
|
||||
export default function RecentSignupPage() {
|
||||
return (
|
||||
<RecentSignupCom />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import UsersCom from '../components/users/UsersCom'
|
||||
|
||||
export default function UsersPage() {
|
||||
return (
|
||||
<UsersCom />
|
||||
)
|
||||
}
|
||||
@@ -5,11 +5,18 @@ 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'],
|
||||
loan_offers: ['loan-offers'],
|
||||
|
||||
// new
|
||||
subscriptions: ['subscriptions'],
|
||||
billings: ['billings'],
|
||||
products: ['products'],
|
||||
users: ['users'],
|
||||
right_sidebar: ['right_sidebar'],
|
||||
recent_signup: ['recent_signup'],
|
||||
}
|
||||
|
||||
export default queryKeys
|
||||
@@ -69,11 +69,34 @@ export const getBillings = (reqData) => {
|
||||
}
|
||||
|
||||
// FUNCTION TO GET SUBSCRIPTIONS
|
||||
export const getSubcriptions = (reqData) => {
|
||||
export const getSubscriptions = (reqData) => {
|
||||
const postData = { ...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 }
|
||||
|
||||
Reference in New Issue
Block a user