Skip to content

Commit 2614dfd

Browse files
committed
fix-pagination-get-all-users
1 parent e1f1a9f commit 2614dfd

File tree

9 files changed

+229
-146
lines changed

9 files changed

+229
-146
lines changed
+96-109
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,87 @@
1+
/* eslint-disable no-mixed-spaces-and-tabs */
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
13
/* eslint-disable react-hooks/exhaustive-deps */
2-
/* eslint-disable @typescript-eslint/no-explicit-any */
3-
import { ReactNode, useEffect, useState } from 'react';
4+
import { ReactNode, useEffect, useRef, useState } from 'react';
45
import { IoMdMore } from 'react-icons/io';
56
import { useAppDispatch, useAppSelector } from '../../redux/hooks/hooks';
67
import { Link } from 'react-router-dom';
7-
import { FaUserCircle } from 'react-icons/fa';
88
import { FaEdit } from 'react-icons/fa';
99
import { userType } from '../../@types/userType';
1010
import { getRoles } from '../../redux/features/getRolesSlice';
1111
import { roleType } from '../../@types/roleTypes';
12-
import { DynamicData } from '../../@types/DynamicData';
1312
import { GrNext, GrPrevious } from 'react-icons/gr';
1413
import ReactPaginate from 'react-paginate';
15-
import { getUser } from '../../redux/features/getUserSlice';
1614
import { HashLoader } from 'react-spinners';
17-
15+
import { getUserPag } from '../../redux/features/getUserPagination';
16+
import useToast from '../../hooks/useToast';
1817
interface getUserType {
1918
arrow?: ReactNode;
2019
searchIcon?: ReactNode;
20+
location?: string;
2121
}
2222
const GetUser = (props: getUserType) => {
2323
const [currentPage, setCurrentPage] = useState(0);
2424
const [searchQuery, setSearchQuery] = useState('');
2525
const [butOverlay, setButOverlay] = useState('');
2626
const roles = useAppSelector((state) => state.allRoles?.data[0]);
2727
const roleLoading = useAppSelector((state) => state.allRoles.isLoading);
28-
const { isLoading, data } = useAppSelector((state) => state.allUsers);
28+
const { isLoading, data } = useAppSelector((state) => state.userPagination);
2929
const pathname = window.location.pathname;
3030
const dispatch = useAppDispatch();
31-
const itemsPerPage = 8;
31+
const prevPage = useRef(0);
32+
const { showErrorMessage } = useToast();
3233

3334
useEffect(() => {
3435
if (!roles) {
35-
dispatch(getRoles()).unwrap();
36+
dispatch(getRoles())
37+
.unwrap()
38+
.catch((error) => {
39+
showErrorMessage(error.message || 'Failed to load wishes');
40+
});
3641
}
3742
}, [dispatch]);
38-
39-
useEffect(() => {
40-
setCurrentPage(0);
41-
}, [searchQuery]);
42-
4343
useEffect(() => {
44-
if (data.length === 0) {
45-
dispatch(getUser()).unwrap();
44+
if (props.location?.includes('users')) {
45+
dispatch(getUserPag({ page: currentPage, search: searchQuery }))
46+
.unwrap()
47+
.catch((error) => {
48+
showErrorMessage(error.message || 'Failed to load wishes');
49+
});
4650
}
47-
}, [dispatch]);
51+
}, [dispatch, currentPage, searchQuery]);
4852

4953
const getRoleName = (roleId: string) => {
5054
return roles?.data.find((role: roleType) => role.id === roleId).roleName;
5155
};
5256
const handleOverlay = (userId: string) => {
5357
setButOverlay(userId === butOverlay ? '' : userId);
5458
};
59+
useEffect(() => {
60+
if (data[data?.length - 1]?.data?.totalPages < prevPage.current) {
61+
setCurrentPage(1);
62+
} else {
63+
setCurrentPage(prevPage.current);
64+
}
65+
}, [searchQuery, data[data?.length - 1]?.data?.totalPages]);
5566
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
5667
setSearchQuery(event.target.value);
68+
if (event.target.value.length < 1) {
69+
setCurrentPage(prevPage.current);
70+
}
71+
if (event.target.value.length === 1 && currentPage !== 1) {
72+
prevPage.current = currentPage;
73+
}
5774
};
58-
const offset = currentPage * itemsPerPage;
59-
const currentItems = data[data.length - 1]?.data
60-
.filter((user: DynamicData) =>
61-
user.email.toLowerCase().includes(searchQuery.toLowerCase()),
62-
)
63-
.slice(offset, offset + itemsPerPage);
64-
let pageCount = 0;
65-
if (data[0]?.data?.length) {
66-
pageCount = Math.ceil(data[0]?.data?.length / itemsPerPage);
67-
}
75+
6876
const handlePageClick = (event: { selected: number }) => {
69-
setCurrentPage(event.selected);
77+
setCurrentPage(event.selected + 1);
78+
prevPage.current = event.selected + 1;
7079
};
7180
const handleButOverlayApp = () => {
7281
if (butOverlay) {
7382
setButOverlay('');
7483
}
7584
};
76-
7785
return (
7886
<div className="relative w-full ipad:w-[93%] ipad:m-auto shadow-2xl rounded-2xl flex flex-col h-full ipad:h-[90%] ipad:pt-6 ">
7987
{isLoading && roleLoading ? (
@@ -97,7 +105,6 @@ const GetUser = (props: getUserType) => {
97105
All Users
98106
</p>
99107
</div>
100-
101108
<div className="py-1 w-full flex items-center justify-center">
102109
<form className="search w-[80%] flex border items-center rounded-xl py-1 px-[2%] overflow-hidden mobile:max-w-[19rem] ">
103110
<p className="text-[29px] flex-none ">{props.searchIcon}</p>
@@ -110,60 +117,40 @@ const GetUser = (props: getUserType) => {
110117
</form>
111118
</div>
112119

113-
<div className="div-table w-full overflow-x-auto ipad:pt-1 ">
114-
<table className=" table-fixed rounded-3xl m-auto shadow-xl ipad:mt-2 ">
115-
<thead className=" w-full text-neutral-white ">
116-
<tr>
117-
<th className=" bg-primary-lightblue px-[3rem] mobile:px-[2rem] py-3 rounded-l-lg ipad:px-[1.5rem] ">
118-
No
119-
</th>
120-
<th className=" bg-primary-lightblue px-[3rem] py-3 ipad:px-[1rem]">
121-
Firstname
122-
</th>
123-
<th className=" bg-primary-lightblue px-[3rem] py-3 ipad:px-[1rem]">
124-
Lastname
125-
</th>
126-
<th className=" bg-primary-lightblue px-[3rem] py-3 ipad:px-[1rem]">
127-
Email
128-
</th>
129-
<th className=" bg-primary-lightblue px-[3rem] mobile:px-[1.5rem] py-3">
130-
Active
131-
</th>
132-
<th className=" bg-primary-lightblue px-[3rem] mobile:px-[3rem] py-3 ipad:px-[2.5rem]">
133-
Role
134-
</th>
135-
<th className=" bg-primary-lightblue px-[3rem] mobile:px-[3rem] py-3 rounded-r-lg ipad:px-[1rem] ">
136-
Action
137-
</th>
120+
<div className="tableWrapper mt-1 text-[1rem] mx-5 laptop:mx-10 bg-neutral-white p-4 rounded-md max-w-[95%]">
121+
<table className="tables pt-2 p-3 overflow-hidden overflow-x-scroll max-w-[18rem] tablet:max-w-[100%]">
122+
<thead className="bg-[#256490] text-neutral-white text-left overflow-hidden rounded-3xl p2">
123+
<tr className="rounded-xl text-sm">
124+
<th className="">No</th>
125+
<th> Firstname</th>
126+
<th> Lastname</th>
127+
<th> Email</th>
128+
<th> Active</th>
129+
<th> Role</th>
130+
<th className="expand">Action</th>
138131
</tr>
139132
</thead>
140-
141-
<tbody className=" text-center rounded-3xl">
142-
{currentItems && currentItems.length > 0 ? (
143-
currentItems.map((item: userType, index: number) => (
144-
<tr
145-
key={item?.id}
146-
className={`${index % 2 !== 0 ? 'bg-neutral-white' : 'bg-overlay bg-opacity-15'}`}
147-
>
148-
<td className=" py-3">{index + 1 + offset}</td>
149-
<td className="pl-[3rem] ipad:pl-[1rem] text-left">
150-
{item?.firstName}
151-
</td>
152-
<td className=" pl-[3rem] ipad:pl-[1rem] text-left">
153-
{' '}
154-
{item?.lastName}
155-
</td>
156-
<td className="ipad:pl-[1rem] pl-[3rem] text-left">
157-
{item?.email}
158-
</td>
159-
<td>
160-
{' '}
161-
{item?.isActive === true ? 'active' : 'unactive'}
162-
</td>
163-
<td> {getRoleName(item.role as string)}</td>
164-
<td>
165-
{' '}
166-
<div className="relative">
133+
<tbody className="text-slate-700">
134+
{data[data?.length - 1]?.data &&
135+
data[data?.length - 1]?.data?.users?.length > 0 ? (
136+
data[data?.length - 1]?.data?.users.map(
137+
(item: userType, index: number) => (
138+
<tr
139+
key={index}
140+
className={`relative text-sm ${index % 2 !== 0 ? 'bg-[#DDDD]' : ''}`}
141+
>
142+
<td className="w-20 h-16">
143+
{index + 1 + data[data?.length - 1]?.data?.offset}
144+
</td>
145+
<td className=" text-sm">{item?.firstName}</td>
146+
<td>{item?.lastName}</td>
147+
<td>{item?.email}</td>
148+
<td>
149+
{' '}
150+
{item?.isActive === true ? 'active' : 'unactive'}
151+
</td>
152+
<td> {getRoleName(item.role as string)}</td>
153+
<td className="cursor-ponter ">
167154
<button
168155
type="button"
169156
onClick={() => {
@@ -174,34 +161,35 @@ const GetUser = (props: getUserType) => {
174161
<IoMdMore />
175162
</button>
176163
{butOverlay === item.id && (
177-
<div
178-
className={` absolute h-[6rem] w-[15rem] rounded shadow-2xl left-20 z-[900] bg-neutral-white px-4 flex flex-col gap-4 tablet:-left-40 py-2
179-
${index + 1 >= currentItems.length - 1 ? 'bottom-0 ' : 'left-20'}
180-
`}
181-
>
182-
<Link to="" className="">
183-
<div className="text-[26px] flex text-center items-center gap-4 bg-neutral-grey px-2 py-1 rounded-xl bg-opacity-35 hover:bg-opacity-70">
184-
<FaUserCircle />
185-
<p className="text-[17px] fontp">
186-
view user details
187-
</p>
164+
<div className="absolute desktop:w-[15%] right-2 laptop:right-16 z-50 -top-10 flex p-2 rounded-lg pt-5 pl-5">
165+
<div className="flex flex-col justify-between relative w-full">
166+
<div className="relative">
167+
<div
168+
className={` absolute h-[3rem] w-[15rem] rounded shadow-2xl right-3 z-[900] bg-neutral-white px-4 flex flex-col gap-4 tablet:-left-40 py-2
169+
${index + 1 === 1 ? 'top-7 ' : ''}
170+
171+
`}
172+
>
173+
<Link
174+
to={`/dashboard/roles/${item.id}`}
175+
className=""
176+
>
177+
<div className="text-[26px] flex text-center items-center gap-4 px-2 py-1 rounded-xl bg-neutral-grey bg-opacity-35 hover:bg-opacity-70">
178+
<FaEdit />
179+
<p className="text-[17px]">
180+
Edit roles
181+
</p>
182+
</div>
183+
</Link>
184+
</div>
188185
</div>
189-
</Link>
190-
<Link
191-
to={`/dashboard/roles/${item.id}`}
192-
className=""
193-
>
194-
<div className="text-[26px] flex text-center items-center gap-4 px-2 py-1 rounded-xl bg-neutral-grey bg-opacity-35 hover:bg-opacity-70">
195-
<FaEdit />
196-
<p className="text-[17px]">Edit roles</p>
197-
</div>
198-
</Link>
186+
</div>
199187
</div>
200188
)}
201-
</div>
202-
</td>
203-
</tr>
204-
))
189+
</td>
190+
</tr>
191+
),
192+
)
205193
) : (
206194
<tr>
207195
<td
@@ -221,7 +209,7 @@ const GetUser = (props: getUserType) => {
221209
nextLabel={<GrNext />}
222210
breakLabel={'...'}
223211
breakClassName={'break-me'}
224-
pageCount={pageCount}
212+
pageCount={data[data?.length - 1]?.data?.totalPages || 0}
225213
marginPagesDisplayed={2}
226214
pageRangeDisplayed={2}
227215
onPageChange={handlePageClick}
@@ -239,5 +227,4 @@ const GetUser = (props: getUserType) => {
239227
</div>
240228
);
241229
};
242-
243230
export default GetUser;

src/pages/Admin/DashboardGetUser.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { MdOutlineSearch } from 'react-icons/md';
22
import { FaArrowCircleLeft } from 'react-icons/fa';
33
import GetUser from '../../components/adminDashboard/getUser';
4+
import { useLocation } from 'react-router-dom';
45
const AdminDashboardAllUser = () => {
6+
const location = useLocation();
57
return (
68
<div className="content h-full pl-5 ipad:pl-0 w-full">
7-
<GetUser searchIcon={<MdOutlineSearch />} arrow={<FaArrowCircleLeft />} />
9+
<GetUser
10+
searchIcon={<MdOutlineSearch />}
11+
arrow={<FaArrowCircleLeft />}
12+
location={location.pathname}
13+
/>
814
</div>
915
);
1016
};

src/pages/Admin/EditUserRoles.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ const EditUser = () => {
1111
const users = useAppSelector(
1212
(state) => state.allUsers.data[state.allUsers.data.length - 1],
1313
);
14-
1514
const getUserInfo = () => {
16-
return users?.data.filter((item: userType) => item?.id === id) || '';
15+
return (
16+
users?.data?.users?.filter((item: userType) => item?.id === id) || ''
17+
);
1718
};
1819
const useR = getUserInfo();
19-
2020
return (
2121
<div className="content h-full ipad:pl-0 w-full">
2222
<GetUser />
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
3+
import { DynamicData } from '../../@types/DynamicData';
4+
import API from '../../utils/api';
5+
6+
interface fetchUserType {
7+
page?: number;
8+
search?: string;
9+
}
10+
11+
interface UsersState {
12+
data: DynamicData[];
13+
isLoading: boolean;
14+
error: string | null;
15+
}
16+
17+
const initialState: UsersState = {
18+
data: [],
19+
isLoading: false,
20+
error: null,
21+
};
22+
23+
export const getUserPag = createAsyncThunk(
24+
'fetchUser',
25+
async ({ page, search }: fetchUserType, { rejectWithValue }) => {
26+
try {
27+
const { data } = await API.get('/users', {
28+
params: { page, search: search },
29+
});
30+
return data;
31+
} catch (error) {
32+
return rejectWithValue(error);
33+
}
34+
},
35+
);
36+
37+
const getUserPagSlice = createSlice({
38+
name: 'users',
39+
initialState,
40+
reducers: {},
41+
extraReducers: (builder) => {
42+
builder.addCase(getUserPag.pending, (state) => {
43+
state.isLoading = true;
44+
state.error = null;
45+
});
46+
builder.addCase(
47+
getUserPag.fulfilled,
48+
(state, action: PayloadAction<any>) => {
49+
state.isLoading = false;
50+
state.data = [...state.data, action.payload];
51+
state.error = null;
52+
},
53+
);
54+
builder.addCase(
55+
getUserPag.rejected,
56+
(state, action: PayloadAction<any>) => {
57+
state.isLoading = false;
58+
state.error = action.payload;
59+
},
60+
);
61+
},
62+
});
63+
64+
export default getUserPagSlice.reducer;

0 commit comments

Comments
 (0)