From 0a95aa1c2c7977b108258571274d18ea7ada6516 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Mon, 1 Sep 2025 20:54:36 +1000 Subject: [PATCH 1/5] refactor: allow pagination and pagesize from the backend --- src/app/TableFooter.tsx | 2 +- src/app/types.ts | 9 +++ src/partner/CorporatePartnerPage.tsx | 4 +- src/partner/api.ts | 17 +++--- .../components/CorporatePartnerList.test.tsx | 57 ++++++++++--------- .../components/CorporatePartnerList.tsx | 39 +++++++++---- src/partner/hooks.ts | 27 ++++++++- 7 files changed, 106 insertions(+), 49 deletions(-) diff --git a/src/app/TableFooter.tsx b/src/app/TableFooter.tsx index f924238..66daaf2 100644 --- a/src/app/TableFooter.tsx +++ b/src/app/TableFooter.tsx @@ -17,7 +17,7 @@ type TableFooterProps = { }; const TableFooter = ({ - pageSizeOptions = [10, 20, 30, 40], + pageSizeOptions = [10, 25, 50, 100], }: TableFooterProps) => { const intl = useIntl(); const { setPageSize, state } = useContext(DataTableContext); diff --git a/src/app/types.ts b/src/app/types.ts index 8cce588..b6c1ce1 100644 --- a/src/app/types.ts +++ b/src/app/types.ts @@ -70,3 +70,12 @@ export interface CellValue { original: T; }; } + +export interface ApiResponse { + count: number; + next?: string | null; + previous?: string | null; + results: T[]; + currentPage?: number; + numPages?: number; +} diff --git a/src/partner/CorporatePartnerPage.tsx b/src/partner/CorporatePartnerPage.tsx index eb828e8..09808b8 100644 --- a/src/partner/CorporatePartnerPage.tsx +++ b/src/partner/CorporatePartnerPage.tsx @@ -1,13 +1,13 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import AppLayout from '../app/AppLayout'; -import CorpotatePartnerList from './components/CorporatePartnerList'; +import CorporatePartnerList from './components/CorporatePartnerList'; import messages from './messages'; const CorpotatePartnerPage = () => { const intl = useIntl(); return ( - + ); }; diff --git a/src/partner/api.ts b/src/partner/api.ts index 2858573..c043cf9 100644 --- a/src/partner/api.ts +++ b/src/partner/api.ts @@ -1,17 +1,20 @@ -import { getConfig, camelCaseObject } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient, camelCaseObject } from '@edx/frontend-platform/auth'; import { logError } from '@edx/frontend-platform/logging'; -import { CorporatePartner } from '../app/types'; +import { CorporatePartner, ApiResponse } from '../app/types'; -export const getPartners = async (): Promise => { - const url = `${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/`; +interface PartnersApiResponse extends ApiResponse { + results: CorporatePartner[]; +} +export const getPartners = async ({ pageSize, pageIndex }): Promise => { + const url = `${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/?page_size=${pageSize}&page=${pageIndex}`; try { const response = await getAuthenticatedHttpClient().get(url); - return camelCaseObject(response.data.results); + return response.data; } catch (error) { logError(error); - return []; + return { results: [], count: 0 }; } }; diff --git a/src/partner/components/CorporatePartnerList.test.tsx b/src/partner/components/CorporatePartnerList.test.tsx index a07012a..319f909 100644 --- a/src/partner/components/CorporatePartnerList.test.tsx +++ b/src/partner/components/CorporatePartnerList.test.tsx @@ -1,5 +1,5 @@ import { screen } from '@testing-library/react'; -import { useSuspenseQuery } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { renderWrapper } from '@src/setupTest'; import CorporatePartnerList from './CorporatePartnerList'; @@ -14,33 +14,38 @@ jest.mock('../../app/TableFooter', () => function TableFooter() { return
; }); -const mockPartners = [ - { - code: 1, - name: 'Example University', - homepage: 'https://exampleu.com/university', - logo: 'https://exampleu.com/logo.png', - catalogs: 5, - courses: 12, - enrollments: 1000, - certified: 400, - }, - { - code: 2, - name: 'Test Institute', - homepage: 'https://test.com/institute', - logo: 'https://test.com/logo.png', - catalogs: 2, - courses: 8, - enrollments: 700, - certified: 300, - }, -]; +const mockApiResponse = { + results: [ + { + code: 1, + name: 'Example University', + homepage: 'https://exampleu.com/university', + logo: 'https://exampleu.com/logo.png', + catalogs: 5, + courses: 12, + enrollments: 1000, + certified: 400, + }, + { + code: 2, + name: 'Test Institute', + homepage: 'https://test.com/institute', + logo: 'https://test.com/logo.png', + catalogs: 2, + courses: 8, + enrollments: 700, + certified: 300, + }, + ], + count: 2, + next: null, + previous: null, +}; describe('CorporatePartnerList', () => { beforeEach(() => { - (useSuspenseQuery as jest.Mock).mockReturnValue({ - data: mockPartners, + (useQuery as jest.Mock).mockReturnValue({ + data: mockApiResponse, isLoading: false, }); }); @@ -72,7 +77,7 @@ describe('CorporatePartnerList', () => { }); it('shows loading state if data is still loading', () => { - (useSuspenseQuery as jest.Mock).mockReturnValue({ + (useQuery as jest.Mock).mockReturnValue({ data: [], isLoading: true, }); diff --git a/src/partner/components/CorporatePartnerList.tsx b/src/partner/components/CorporatePartnerList.tsx index 7f3fe77..2be699f 100644 --- a/src/partner/components/CorporatePartnerList.tsx +++ b/src/partner/components/CorporatePartnerList.tsx @@ -1,4 +1,3 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; import { useIntl } from '@edx/frontend-platform/i18n'; import { DataTable, TextFilter, @@ -8,11 +7,12 @@ import { CorporatePartner } from '@src/app/types'; import TableName from '@src/app/TableName'; import { useNavigate } from '@src/hooks'; import { paths } from '@src/constants'; -import { getPartners } from '../api'; +import { useCallback, useState } from 'react'; import TableFooter from '../../app/TableFooter'; import messages from '../messages'; import ActionItem from '../../app/ActionItem'; +import { usePartners } from '../hooks'; type CellValue = { row: { @@ -22,13 +22,24 @@ type CellValue = { const tableActions = ['view']; -const CorpotatePartnerList = () => { +const CorporatePartnerList = () => { const navigate = useNavigate(); const intl = useIntl(); - const { data, isLoading } = useSuspenseQuery({ - queryKey: ['partners'], - queryFn: () => getPartners(), - }); + const [pageSize, setPageSize] = useState(10); + const [pageIndex, setPageIndex] = useState(0); + const { + partners, isLoading, count, pages, + } = usePartners({ pageSize, pageIndex: pageIndex + 1 }); + + // This function will be called by DataTable when pagination, filters, or sorting change + const fetchData = useCallback((tableState) => { + if (pageIndex !== tableState.pageIndex) { + setPageIndex(tableState.pageIndex); + } + if (pageSize !== tableState.pageSize) { + setPageSize(tableState.pageSize); + } + }, [pageIndex, pageSize]); return ( { isFilterable defaultColumnValues={{ Filter: TextFilter }} initialState={{ - pageSize: 30, - pageIndex: 0, + pageSize: 10, + pageIndex, }} + autoResetPageIndex={false} // turn off auto reset of pageIndex + data={partners} + itemCount={count} + pageCount={pages} + fetchData={fetchData} + manualPagination additionalColumns={[ { id: 'action', @@ -53,8 +70,6 @@ const CorpotatePartnerList = () => { )), }, ]} - itemCount={data.length} - data={data} columns={[ { Header: intl.formatMessage(messages.headerName), @@ -94,4 +109,4 @@ const CorpotatePartnerList = () => { ); }; -export default CorpotatePartnerList; +export default CorporatePartnerList; diff --git a/src/partner/hooks.ts b/src/partner/hooks.ts index 5f550d5..2913474 100644 --- a/src/partner/hooks.ts +++ b/src/partner/hooks.ts @@ -1,6 +1,6 @@ import { CorporatePartner } from '@src/app/types'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { getPartnerDetails } from './api'; +import { getPartnerDetails, getPartners } from './api'; export const usePartnerDetails = ({ partnerId }) => { const queryClient = useQueryClient(); @@ -20,3 +20,28 @@ export const usePartnerDetails = ({ partnerId }) => { isLoadingPartnerDetails: isLoading, }; }; + + +interface UsePartnersOptions { + pageSize: number; + pageIndex: number; +} + +export function usePartners({ pageSize, pageIndex }: UsePartnersOptions) { + const { + data, + isLoading, + error, + } = useQuery({ + queryKey: ['partners', pageSize, pageIndex], + queryFn: () => getPartners({ pageSize, pageIndex }), + }); + + return { + isLoading, + partners: data?.results?.map((partnerData) => camelCaseObject(partnerData)) || [], + count: data?.count || 0, + pages: data?.numPages || 1, + error, + }; +} From 1d1cb3d762652b04d3c8d4a408dbdfc1206ce530 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Wed, 5 Nov 2025 16:10:37 +1100 Subject: [PATCH 2/5] refactor: update API endpoints --- src/catalogs/api.ts | 4 ++-- src/courses/api.ts | 4 ++-- src/partner/api.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/catalogs/api.ts b/src/catalogs/api.ts index 459c0bb..e7a545a 100644 --- a/src/catalogs/api.ts +++ b/src/catalogs/api.ts @@ -10,7 +10,7 @@ export const getPartnerCatalogs = async ( pageSize: number, ): Promise> => { try { - const url = new URL(`${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/${partnerId}/catalogs/`); + const url = new URL(`${getConfig().LMS_BASE_URL}/partner_catalog/api/v1/partners/${partnerId}/catalogs/`); url.searchParams.append('page', page.toString()); url.searchParams.append('page_size', pageSize.toString()); @@ -35,7 +35,7 @@ export const getCatalogDetails = async ( catalogId: string | number, ): Promise => { try { - const url = `${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/${partnerId}/catalogs/${catalogId}/`; + const url = `${getConfig().LMS_BASE_URL}/partner_catalog/api/v1/partners/${partnerId}/catalogs/${catalogId}/`; const response = await getAuthenticatedHttpClient().get(url); return camelCaseObject(response.data); } catch (error) { diff --git a/src/courses/api.ts b/src/courses/api.ts index bcbfca0..79c527e 100644 --- a/src/courses/api.ts +++ b/src/courses/api.ts @@ -6,7 +6,7 @@ import { CorporateCourse, PaginatedResponse } from '@src/app/types'; export const getCourses = async (partnerId: string, catalogId: string, pageIndex, pageSize) : Promise> => { try { - const url = new URL(`${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/${partnerId}/catalogs/${catalogId}/courses/`); + const url = new URL(`${getConfig().LMS_BASE_URL}/partner_catalog/api/v1/partners/${partnerId}/catalogs/${catalogId}/courses/`); url.searchParams.append('page', pageIndex); url.searchParams.append('page_size', pageSize); const { data } = await getAuthenticatedHttpClient().get(url); @@ -21,7 +21,7 @@ export const getCourses = async (partnerId: string, catalogId: string, pageIndex export const deleteCourse = async (partnerId: string, catalogId: string, courseId: number) => { try { - await getAuthenticatedHttpClient().delete(`${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/${partnerId}/catalogs/${catalogId}/courses/${courseId}/`); + await getAuthenticatedHttpClient().delete(`${getConfig().LMS_BASE_URL}/partner_catalog/api/v1/partners/${partnerId}/catalogs/${catalogId}/courses/${courseId}/`); } catch (error) { logError(error); throw error; diff --git a/src/partner/api.ts b/src/partner/api.ts index c043cf9..b31379c 100644 --- a/src/partner/api.ts +++ b/src/partner/api.ts @@ -8,7 +8,7 @@ interface PartnersApiResponse extends ApiResponse { results: CorporatePartner[]; } export const getPartners = async ({ pageSize, pageIndex }): Promise => { - const url = `${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/?page_size=${pageSize}&page=${pageIndex}`; + const url = `${getConfig().LMS_BASE_URL}/partner_catalog/api/v1/partners/?page_size=${pageSize}&page=${pageIndex}`; try { const response = await getAuthenticatedHttpClient().get(url); return response.data; @@ -20,7 +20,7 @@ export const getPartners = async ({ pageSize, pageIndex }): Promise => { try { - const url = `${getConfig().LMS_BASE_URL}/corporate_access/api/v1/partners/${partnerId}/`; + const url = `${getConfig().LMS_BASE_URL}/partner_catalog/api/v1/partners/${partnerId}/`; const response = await getAuthenticatedHttpClient().get(url); return camelCaseObject(response.data); } catch (error) { From a9f4a208961d8d48c680ad379ab05e41de3b409a Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Wed, 5 Nov 2025 18:01:39 +1100 Subject: [PATCH 3/5] refactor: improve TableName alignment --- src/app/TableName.tsx | 2 +- src/partner/api.ts | 6 +++--- src/partner/hooks.ts | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/TableName.tsx b/src/app/TableName.tsx index 0759227..c6460cf 100644 --- a/src/app/TableName.tsx +++ b/src/app/TableName.tsx @@ -16,7 +16,7 @@ const TableName = ({ destination={destination} variant="muted" > - + {image && ( {`${name} { }; }; - interface UsePartnersOptions { pageSize: number; pageIndex: number; @@ -39,7 +38,7 @@ export function usePartners({ pageSize, pageIndex }: UsePartnersOptions) { return { isLoading, - partners: data?.results?.map((partnerData) => camelCaseObject(partnerData)) || [], + partners: data?.results || [], count: data?.count || 0, pages: data?.numPages || 1, error, From 498051fbb6d1ac2fc55d296d6291a2b7003e0915 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Wed, 5 Nov 2025 18:15:28 +1100 Subject: [PATCH 4/5] refactor: use pageSize from the hook as defaut in the partner list --- src/partner/components/CorporatePartnerList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/partner/components/CorporatePartnerList.tsx b/src/partner/components/CorporatePartnerList.tsx index 2be699f..dbcc4f8 100644 --- a/src/partner/components/CorporatePartnerList.tsx +++ b/src/partner/components/CorporatePartnerList.tsx @@ -48,7 +48,7 @@ const CorporatePartnerList = () => { isFilterable defaultColumnValues={{ Filter: TextFilter }} initialState={{ - pageSize: 10, + pageSize, pageIndex, }} autoResetPageIndex={false} // turn off auto reset of pageIndex From bb3360d57307828cbb7f5e9205841ab8f0866d73 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Thu, 6 Nov 2025 05:50:54 +1100 Subject: [PATCH 5/5] fix: remove unnecessary datatable value --- src/partner/components/CorporatePartnerList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/partner/components/CorporatePartnerList.tsx b/src/partner/components/CorporatePartnerList.tsx index dbcc4f8..c592e40 100644 --- a/src/partner/components/CorporatePartnerList.tsx +++ b/src/partner/components/CorporatePartnerList.tsx @@ -51,7 +51,6 @@ const CorporatePartnerList = () => { pageSize, pageIndex, }} - autoResetPageIndex={false} // turn off auto reset of pageIndex data={partners} itemCount={count} pageCount={pages}