Merge branch 'ref-strict-validation' of DigiFi/digifi-www into master
This commit is contained in:
@@ -3,13 +3,13 @@ import NairaBag from '../../assets/images/dashboard/naira-bag.png';
|
||||
import {useNavigate} from 'react-router-dom'
|
||||
import { Button, Icons } from '../';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PendingList from '../paginated-list/PendingList';
|
||||
import { PendingTableList } from '../../core/models';
|
||||
import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter';
|
||||
import { getUserPendingLoanList } from '../../core/apiRequest';
|
||||
import {FormatAmount} from '../../lib/FormatAmount'
|
||||
import PendingLoanPopout from './PendingLoanPopout';
|
||||
import { RouteHandler } from '../../router/routes';
|
||||
import TableWrapper from '../tableWrapper/TableWrapper';
|
||||
|
||||
export interface DashBoardCardProps {
|
||||
title?: string;
|
||||
@@ -170,58 +170,76 @@ const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({
|
||||
)}
|
||||
{userLoanList.loading ? null : (
|
||||
<div className="mt-5 w-full">
|
||||
<PendingList
|
||||
<TableWrapper
|
||||
data={userLoanList.data}
|
||||
itemsPerPage={5}
|
||||
tableTitle="Current Applications"
|
||||
itemsPerPage={7}
|
||||
>
|
||||
{(data: any) => (
|
||||
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
|
||||
<table className="text-[12px] sm:text-base w-full table-auto">
|
||||
<thead>
|
||||
<tr className="text-left border-b-2">
|
||||
<th className="px-1 py-4">Date</th>
|
||||
<th className="px-1 py-4 text-right">Amount</th>
|
||||
<th className="px-1 py-4 text-center min-w-[110px]">
|
||||
Payment Term
|
||||
</th>
|
||||
<th className="px-1 py-4 text-center">Status</th>
|
||||
<th className="px-1 py-4 text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((item: any, index: any) => (
|
||||
<tr key={index || item} className="even:bg-slate-100">
|
||||
<td className="px-1 py-2">
|
||||
{NewDateTimeFormatter(item?.added)}
|
||||
</td>
|
||||
<td className="px-1 py-2 text-right">
|
||||
{FormatAmount(item?.loan_amount)}
|
||||
</td>
|
||||
<td className="px-1 py-2 text-center">
|
||||
{item?.payment_month}
|
||||
</td>
|
||||
<td className="px-1 py-2 text-center">
|
||||
<button
|
||||
className={`${!item?.status_text?.button && 'pointer-events-none border-0'} border p-2`}
|
||||
onClick={()=>setLoanPopout({show:true, data:item})}
|
||||
>
|
||||
{item?.status_text?.text || 'Pending'}
|
||||
</button>
|
||||
</td>
|
||||
<td className="flex justify-end px-1 py-2 text-right">
|
||||
<button className="flex flex-nowrap items-center px-2 py-1 border-2 border-black" onClick={()=>navigate(RouteHandler.dashboardReference, {state:{application_uid: item?.application_uid}})}>
|
||||
View
|
||||
<Icons name="arrow-right" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
{({ data }:{data:any}) => (
|
||||
<>
|
||||
<div className='w-full h-[420px] overflow-auto'>
|
||||
<table className="py-2 w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Date
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Amount
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Payment Term
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(data && data.length > 0) ? data?.map((item:any) => (
|
||||
<tr key={item?.application_uid} className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td className="px-3 py-2">
|
||||
{NewDateTimeFormatter(item?.added)}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
{FormatAmount(item?.loan_amount)}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
{item?.payment_month}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button
|
||||
className={`${!item?.status_text?.button && 'pointer-events-none border-0'} border p-2`}
|
||||
onClick={()=>setLoanPopout({show:true, data:item})}
|
||||
>
|
||||
{item?.status_text?.text || 'Pending'}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-3 py-2 flex gap-2">
|
||||
<button className="flex flex-nowrap items-center px-2 py-1 border-2 border-black" onClick={()=>navigate(RouteHandler.dashboardReference, {state:{application_uid: item?.application_uid}})}>
|
||||
View
|
||||
<Icons name="arrow-right" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
:
|
||||
<tr className="w-3 p-3">
|
||||
<td className="px-3 py-2" colSpan={5}>
|
||||
<div className="flex justify-center items-center">
|
||||
No Record Found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</PendingList>
|
||||
</>
|
||||
)}
|
||||
</TableWrapper>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -42,16 +42,20 @@ const validationSchema = Yup.object().shape({
|
||||
.min(11, "must be 11 digits")
|
||||
.max(11, "must be 11 digits"),
|
||||
ref_two_name: Yup.string()
|
||||
.required("Required"),
|
||||
.notOneOf([Yup.ref('ref_name')], "Name cannot be the same")
|
||||
.required("Required"),
|
||||
ref_two_email: Yup.string()
|
||||
.email("Invalid")
|
||||
.notOneOf([Yup.ref('ref_email')], "Email cannot be the same")
|
||||
.required("Required"),
|
||||
ref_two_phone_number: Yup.string()
|
||||
.notOneOf([Yup.ref('ref_phone_number')], "Phone number cannot be the same")
|
||||
.required("Required"),
|
||||
ref_two_relationship: Yup.string()
|
||||
.required("Required"),
|
||||
ref_two_bvn: Yup.string()
|
||||
.required("BVN is required")
|
||||
.notOneOf([Yup.ref('ref_bvn')], "BVN number cannot be the same")
|
||||
.required("Required")
|
||||
.test("no-e", "Invalid number", (value:any) => {
|
||||
if (value && /^[0-9]*$/.test(value) == false) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
type Props ={
|
||||
text: string
|
||||
onClick?: ()=>any
|
||||
className?: string
|
||||
shrinkAside?: boolean
|
||||
icon?: string
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function MainBtn({
|
||||
onClick,
|
||||
className,
|
||||
text,
|
||||
shrinkAside,
|
||||
icon,
|
||||
loading,
|
||||
disabled
|
||||
}:Props) {
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
className={`py-3 px-4 rounded-md ${className || ''} ${(disabled || loading) && 'opacity-60'}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{icon && <i className={`fa-solid ${icon}`}></i>}
|
||||
{shrinkAside ? '' : loading? 'Loading...' : text}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import MainBtn from "../MainBtn";
|
||||
|
||||
const data1:{ name: string; email: string; status: string; location: string; }[] = [];
|
||||
|
||||
type PaginatedListProps = {
|
||||
data: any,
|
||||
itemsPerPage: number,
|
||||
filterItem?: string[],
|
||||
titleClass?:string,
|
||||
children: (data:any) => ReactNode;
|
||||
}
|
||||
|
||||
export default function TableWrapper({
|
||||
data = data1,
|
||||
itemsPerPage = 5,
|
||||
filterItem,
|
||||
children,
|
||||
}:PaginatedListProps) {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filteredData, setFilteredData] = useState(data);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [newData, setNewData] = useState<{ name: string; email: string; status: string; location: string; }[]>([]);
|
||||
|
||||
const numberOfSelection = itemsPerPage;
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentPage != 0) {
|
||||
setCurrentPage((prev:any) => prev - numberOfSelection);
|
||||
}
|
||||
};
|
||||
const handleNext = () => {
|
||||
if (currentPage < data.length) {
|
||||
setCurrentPage((prev:any) => prev + numberOfSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = ({ target: { value } }:{target: {value:string}}, name:string) => {
|
||||
setSearchTerm(value);
|
||||
let newFilteredData:any = data.filter((item:any) =>
|
||||
item[name].toLowerCase().startsWith(value.toLowerCase())
|
||||
);
|
||||
setFilteredData(newFilteredData);
|
||||
setCurrentPage(0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
setTimeout(()=>{
|
||||
setNewData(
|
||||
filteredData?.slice(currentPage, numberOfSelection + currentPage)
|
||||
);
|
||||
setIsLoading(false)
|
||||
},1000)
|
||||
}, [currentPage, filteredData]);
|
||||
|
||||
useEffect(()=>{
|
||||
setCurrentPage(0)
|
||||
},[itemsPerPage])
|
||||
|
||||
return (
|
||||
<div className="p-2 w-full bg-white border-b dark:bg-gray-800 rounded-md">
|
||||
{data.length > 0 && filterItem && (
|
||||
<div className="mb-10 flex justify-end items-center gap-2">
|
||||
{filterItem.map((item, index) => (
|
||||
<label
|
||||
key={index}
|
||||
className="flex flex-col sm:flex-row items-center gap-2 text-slate-600 dark:text-slate-100 transition-all duration-500"
|
||||
>
|
||||
Search by {item[0].toUpperCase() + item.slice(1)}
|
||||
<input
|
||||
name={item}
|
||||
type="text"
|
||||
className="py-1 px-2 text-sm min-w-[100px] text-black dark:text-white bg-white dark:bg-slate-800 rounded-full border-0 outline-none ring-1 ring-slate-300 dark:ring-white transition-all duration-500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
handleSearch(e, item);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full">
|
||||
{children({ data: newData })}
|
||||
</div>
|
||||
|
||||
{/* PAGINATION BUTTON */}
|
||||
{(newData.length > 0 && data.length > itemsPerPage) &&
|
||||
<div className='p-2 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">
|
||||
{isLoading ? '----' : `${currentPage + 1}-${currentPage + numberOfSelection >= data.length ? data.length : (currentPage + numberOfSelection)}`}</span> of <span className="font-semibold text-gray-900 dark:text-white">{data.length}</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-3 md:gap-6'>
|
||||
<MainBtn
|
||||
onClick={handlePrev}
|
||||
text='Prev'
|
||||
className={`${currentPage == 0 ? 'bg-sky-600/50 pointer-events-none' : 'bg-sky-600'} text-white-light`}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<MainBtn
|
||||
onClick={handleNext}
|
||||
text='Next'
|
||||
className={`${currentPage + numberOfSelection >= data.length ? 'bg-sky-600/50 pointer-events-none' : 'bg-sky-600'} text-white-light`}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* show prev and next button if data exist */}
|
||||
{/* {data.length > 0 && (
|
||||
{data.length && data.map((item, index)=>{
|
||||
item = item
|
||||
if(index%itemsPerPage == 0 && index >= currentPage && index <= currentPage+itemsPerPage){
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={handleNext}
|
||||
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center border transition-all duration-300 ${
|
||||
currentPage != index
|
||||
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400"
|
||||
: "text-slate-600 border-slate-600 dark:text-white dark:border-white pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
{index/itemsPerPage +1}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)} */}
|
||||
{isLoading && <TableIsLoading />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TableIsLoading = () => {
|
||||
return (
|
||||
<div className="w-full fixed z-[991] inset-0 flex justify-center items-center bg-white/50">
|
||||
<p className="rounded-md shadow-md p-4 bg-white/90 dark:bg-gray-900 text-brown dark:text-white">Loading...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -83,4 +83,12 @@ export const addCard = (postData:any) => {
|
||||
...postData
|
||||
}
|
||||
return postAuxEnd('/addcard', reqData)
|
||||
}
|
||||
|
||||
// FUNCTION TO GET USER AGREEMENTS LIST
|
||||
export const getAgreementsList = () => {
|
||||
let reqData = {
|
||||
uid: localStorage.getItem('uid'),
|
||||
}
|
||||
return getAuxEnd(`/agreements?uid=${reqData?.uid}`, null)
|
||||
}
|
||||
@@ -1,5 +1,96 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import TableWrapper from "../components/tableWrapper/TableWrapper";
|
||||
import { getAgreementsList } from '../core/apiRequest';
|
||||
import { NewDateTimeFormatter } from '../lib/NewDateTimeFormatter';
|
||||
import { FormatAmount } from '../lib/FormatAmount';
|
||||
|
||||
export default function DashboardLegalsPage() {
|
||||
|
||||
const [agreementList, setAgreementList] = useState({loading:true, data:[]})
|
||||
|
||||
useEffect(()=>{
|
||||
getAgreementsList()
|
||||
.then((res) => {
|
||||
if (!res || !res.data) {
|
||||
setAgreementList({ loading: false, data: [] });
|
||||
return;
|
||||
}
|
||||
setAgreementList({ loading: false, data: res?.data });
|
||||
})
|
||||
.catch((err) => {
|
||||
setAgreementList({ loading: false, data: [] });
|
||||
console.log(err)
|
||||
});
|
||||
},[])
|
||||
|
||||
return (
|
||||
<div>DashboardLegals</div>
|
||||
<div className='w-full'>
|
||||
<h1 className='my-3 text-3xl font-bold'>Agreements</h1>
|
||||
{agreementList.loading ? null : (
|
||||
<div className="mt-5 w-full">
|
||||
<TableWrapper
|
||||
data={agreementList.data}
|
||||
itemsPerPage={7}
|
||||
>
|
||||
{({ data }:{data:any}) => (
|
||||
<>
|
||||
<div className='w-full min-h-[300px] overflow-auto'>
|
||||
<table className="py-2 w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Date
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Amount
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Payment Term
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-2">
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(data && data.length > 0) ? data?.map((item:any) => (
|
||||
<tr key={item.loan_uid} className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td className="px-3 py-2">
|
||||
{NewDateTimeFormatter(item?.added)}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
{FormatAmount(item?.loan_amount)}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
{item?.payment_month}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button
|
||||
className={`${!item?.status_text?.button && 'pointer-events-none border-0'} border p-2`}
|
||||
// onClick={()=>setLoanPopout({show:true, data:item})}
|
||||
>
|
||||
{item?.status_text?.text || 'Pending'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
:
|
||||
<tr className="w-3 p-3">
|
||||
<td className="px-3 py-2" colSpan={5}>
|
||||
<div className="flex justify-center items-center">
|
||||
No Record Found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</TableWrapper>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user