Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pagination support for frontend #182

Merged
merged 10 commits into from
Sep 9, 2024
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.0",
"react-intersection-observer": "^9.13.1",
"react-router": "^6.11.2",
"react-router-dom": "^6.11.2",
"zod": "^3.22.4"
Expand Down
79 changes: 63 additions & 16 deletions src/hooks/admin/useMentees.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
InfiniteData,
QueryFunctionContext,
useInfiniteQuery,
UseInfiniteQueryOptions,
useMutation,
useQueryClient,
} from '@tanstack/react-query';
import { API_URL } from '../../constants';
import axios, { type AxiosError } from 'axios';
import { type Mentee } from '../../types';
Expand All @@ -9,6 +16,31 @@ interface MenteeStatus {
status: ApplicationStatus;
}

interface MenteeResponse {
items: Mentee[];
message: string;
pageNumber: number;
pageSize: number;
totalItemCount: number;
}

type MenteesQueryKey = ['admin-mentees', string | null, number];

const fetchMentees = async ({
pageParam = 1,
queryKey,
}: QueryFunctionContext<MenteesQueryKey, number>): Promise<MenteeResponse> => {
const [, menteeStatus, pageSize] = queryKey;
let url = `${API_URL}/admin/mentees/applications?pageNumber=${pageParam}&pageSize=${pageSize}`;
if (menteeStatus !== null && menteeStatus !== '') {
url += `&status=${menteeStatus}`;
}
const response = await axios.get(url, {
withCredentials: true,
});
return response.data;
};

const updateMenteeStatus = async ({ menteeId, status }: MenteeStatus) => {
const response = await axios.put(
`${API_URL}/admin/mentees/${menteeId}/status`,
Expand All @@ -18,21 +50,30 @@ const updateMenteeStatus = async ({ menteeId, status }: MenteeStatus) => {
return response.data;
};

const useMentees = () => {
const useMentees = (menteeStatus: string | null, pageSize = 10) => {
const queryClient = useQueryClient();

const { isLoading, error, data } = useQuery({
queryKey: ['admin-mentees'],
queryFn: async () => {
const { data } = await axios.get(
`${API_URL}/admin/mentees/applications`,
{
withCredentials: true,
}
);
return data.mentees as Mentee[];
const infiniteQueryOptions: UseInfiniteQueryOptions<
MenteeResponse,
AxiosError,
InfiniteData<MenteeResponse, number>,
MenteeResponse,
MenteesQueryKey,
number
> = {
queryKey: ['admin-mentees', menteeStatus, pageSize],
queryFn: fetchMentees,
getNextPageParam: (lastPage) => {
const nextPage = lastPage.pageNumber + 1;
return nextPage <= Math.ceil(lastPage.totalItemCount / lastPage.pageSize)
? nextPage
: undefined;
},
});
initialPageParam: 1,
};

const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } =
useInfiniteQuery(infiniteQueryOptions);

const { mutateAsync: updateStatus } = useMutation<
unknown,
Expand All @@ -45,11 +86,17 @@ const useMentees = () => {
},
});

const mentees = data?.pages.flatMap((page) => page.items) ?? [];
const totalItemCount = data?.pages[0]?.totalItemCount ?? 0;

return {
isLoading,
error,
data,
data: mentees,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
status,
updateStatus,
totalItemCount,
};
};

Expand Down
74 changes: 58 additions & 16 deletions src/hooks/admin/useMentors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
useQuery,
useInfiniteQuery,
useMutation,
useQueryClient,
type QueryFunction,
type QueryKey,
type InfiniteData,
type QueryFunctionContext,
type UseInfiniteQueryOptions,
} from '@tanstack/react-query';
import axios, { type AxiosError } from 'axios';
import { API_URL } from '../../constants';
Expand All @@ -14,18 +15,32 @@ interface MentorStatus {
state: string;
}

const fetchMentors: QueryFunction<Mentor[], QueryKey> = async ({
interface MentorResponse {
items: Mentor[];
message: string;
pageNumber: number;
pageSize: number;
totalItemCount: number;
}

type MentorsQueryKey = ['mentors', string | null, string | null, number];

const fetchMentors = async ({
pageParam = 1,
queryKey,
}) => {
const [, category]: [string, string] = queryKey as [string, string];
let url = `${API_URL}/admin/mentors`;
}: QueryFunctionContext<MentorsQueryKey, number>): Promise<MentorResponse> => {
const [, category, mentorStatus, pageSize] = queryKey;
let url = `${API_URL}/admin/mentors?pageNumber=${pageParam}&pageSize=${pageSize}`;
if (category !== '' && category != null) {
url += `?categoryId=${category}`;
url += `&categoryId=${category}`;
}
if (mentorStatus !== null && mentorStatus !== '') {
url += `&status=${mentorStatus}`;
}
const response = await axios.get(url, {
withCredentials: true,
});
return response.data.mentors;
return response.data;
};

const updateMentorStatus = async (mentorStatus: MentorStatus) => {
Expand All @@ -38,13 +53,34 @@ const updateMentorStatus = async (mentorStatus: MentorStatus) => {
return response.data;
};

export const useMentors = (categoryId?: string) => {
export const useMentors = (
categoryId: string | null,
mentorStatus: string | null,
pageSize = 10
) => {
const queryClient = useQueryClient();

const { isLoading, error, data } = useQuery<Mentor[], AxiosError>({
queryKey: ['mentors', categoryId],
const infiniteQueryOptions: UseInfiniteQueryOptions<
MentorResponse,
AxiosError,
InfiniteData<MentorResponse, number>,
MentorResponse,
MentorsQueryKey,
number
> = {
queryKey: ['mentors', categoryId, mentorStatus, pageSize],
queryFn: fetchMentors,
});
initialPageParam: 1,
getNextPageParam: (lastPage) => {
const nextPage = lastPage.pageNumber + 1;
return nextPage <= Math.ceil(lastPage.totalItemCount / lastPage.pageSize)
? nextPage
: undefined;
},
};

const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } =
useInfiniteQuery(infiniteQueryOptions);

const { mutateAsync: updateStatus } = useMutation<
unknown,
Expand All @@ -57,10 +93,16 @@ export const useMentors = (categoryId?: string) => {
},
});

const mentors = data?.pages.flatMap((page) => page.items) ?? [];
const totalItemCount = data?.pages[0]?.totalItemCount ?? 0;

return {
isLoading,
error,
data,
data: mentors,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
status,
updateStatus,
totalItemCount,
};
};
47 changes: 33 additions & 14 deletions src/hooks/useCategories.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import { useQuery } from '@tanstack/react-query';
import { useInfiniteQuery } from '@tanstack/react-query';
import { API_URL } from '../constants';
import axios from 'axios';
import { type Category } from '../types';

const useCategories = () => {
const { isLoading, error, data } = useQuery({
queryKey: ['categories'],
initialData: [],
queryFn: async () => {
const { data } = await axios.get(`${API_URL}/categories`);
return data.categories;
interface CategoriesResponse {
items: Category[];
totalItemCount: number;
pageNumber: number;
pageSize: number;
}

const fetchCategories = async ({
pageParam,
queryKey,
}: {
pageParam: number;
queryKey: Array<string | number | null>;
}): Promise<CategoriesResponse> => {
const [pageSize] = queryKey as [number];
const response = await axios.get<CategoriesResponse>(
`${API_URL}/categories?pageNumber=${pageParam}&pageSize=${pageSize}`
);
return response.data;
};

const useCategories = (pageNumber = 1, pageSize = 10) => {
return useInfiniteQuery({
queryKey: [pageNumber, pageSize],
queryFn: fetchCategories,
initialPageParam: 1,
getNextPageParam: (lastPage, pages) => {
if (lastPage.items.length < pageSize) {
return undefined;
}
return pages.length + 1;
},
});

return {
isLoading,
error,
data,
};
};

export default useCategories;
54 changes: 32 additions & 22 deletions src/hooks/usePublicMentors.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
import {
useQuery,
type QueryFunction,
type QueryKey,
} from '@tanstack/react-query';
import axios, { type AxiosError } from 'axios';
import { useInfiniteQuery } from '@tanstack/react-query';
import axios from 'axios';
import { API_URL } from '../constants';
import { type Mentor } from '../types';

const fetchPublicMentors: QueryFunction<Mentor[], QueryKey> = async ({
interface MentorsResponse {
items: Mentor[];
totalItemCount: number;
pageNumber: number;
pageSize: number;
}

const fetchPublicMentors = async ({
pageParam,
queryKey,
}) => {
const [, category]: [string, string] = queryKey as [string, string];
let url = `${API_URL}/mentors`;
}: {
pageParam: number;
queryKey: Array<string | number | null>;
}): Promise<MentorsResponse> => {
const [, category, pageSize] = queryKey;
let url = `${API_URL}/mentors?pageNumber=${pageParam}&pageSize=${
pageSize ?? ''
}`;
if (category != null) {
url += `?categoryId=${category}`;
url += `&categoryId=${category}`;
}
const response = await axios.get(url, {
const response = await axios.get<MentorsResponse>(url, {
withCredentials: true,
});
return response.data.mentors;
return response.data;
};

export const usePublicMentors = (categoryId: string | null) => {
const { isLoading, error, data } = useQuery<Mentor[], AxiosError>({
queryKey: ['public-mentors', categoryId],
export const usePublicMentors = (categoryId: string | null, pageSize = 10) => {
return useInfiniteQuery({
queryKey: ['public-mentors', categoryId, pageSize],
queryFn: fetchPublicMentors,
initialPageParam: 1,
getNextPageParam: (lastPage, pages) => {
if (lastPage.items.length < pageSize) {
return undefined;
}
return pages.length + 1;
},
});

return {
isLoading,
error,
data,
};
};
Loading
Loading