diff --git a/CHANGELOG.md b/CHANGELOG.md index 7342e6b..1f831e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to ### Added +- 🐛(front) fix missing pagination mail domains - 🐛(front) fix button add mail domain - ✨(teams) add matrix webhook for teams #904 - ✨(resource-server) add SCIM /Me endpoint #895 diff --git a/src/frontend/apps/desk/src/features/mail-domains/domains/components/panel/MailDomainsListView.tsx b/src/frontend/apps/desk/src/features/mail-domains/domains/components/panel/MailDomainsListView.tsx index 7488f31..5d0ea96 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/domains/components/panel/MailDomainsListView.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/domains/components/panel/MailDomainsListView.tsx @@ -1,5 +1,5 @@ import { Button, DataGrid } from '@openfun/cunningham-react'; -import { useMemo } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, StyledLink, Tag, Text } from '@/components'; @@ -17,7 +17,8 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) { const { t } = useTranslation(); const { ordering } = useMailDomainsStore(); - const { data, isLoading } = useMailDomains({ ordering }); + const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = + useMailDomains({ ordering }); const mailDomains = useMemo(() => { return data?.pages.reduce((acc, page) => { return acc.concat(page.results); @@ -38,6 +39,31 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) { ); }, [querySearch, mailDomains]); + const loadMoreRef = useRef(null); + + useEffect(() => { + if (!hasNextPage) { + return; + } + const ref = loadMoreRef.current; + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) { + void fetchNextPage(); + } + }, + { threshold: 1 }, + ); + if (ref) { + observer.observe(ref); + } + return () => { + if (ref) { + observer.unobserve(ref); + } + }; + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + return (
{filteredMailDomains && filteredMailDomains.length ? ( @@ -47,17 +73,17 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) { columns={[ { field: 'name', - headerName: 'Domaine', + headerName: `${t('Domain')} (${filteredMailDomains.length})`, enableSorting: true, }, { field: 'count_mailboxes', - headerName: "Nombre d'adresses", + headerName: `${t('Number of mailboxes')}`, enableSorting: true, }, { id: 'status', - headerName: 'Statut', + headerName: `${t('Status')}`, enableSorting: true, renderCell({ row }) { return ( @@ -96,6 +122,8 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) { isLoading={isLoading} /> ) : null} +
+ {isFetchingNextPage &&
{t('Loading more...')}
} {!filteredMailDomains || (!filteredMailDomains.length && ( diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/api/useMailboxesInfinite.tsx b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/api/useMailboxesInfinite.tsx new file mode 100644 index 0000000..2db73b1 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/api/useMailboxesInfinite.tsx @@ -0,0 +1,45 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { APIError, APIList } from '@/api'; + +import { MailDomainMailbox } from '../types'; + +import { + KEY_LIST_MAILBOX, + MailDomainMailboxesParams, + getMailDomainMailboxes, +} from './useMailboxes'; + +// Redéfinition locale du type +type MailDomainMailboxesResponse = APIList; + +export function useMailboxesInfinite( + param: Omit, + queryConfig = {}, +) { + return useInfiniteQuery< + MailDomainMailboxesResponse, + APIError, + MailDomainMailboxesResponse, + [string, Omit], + number + >({ + queryKey: [KEY_LIST_MAILBOX, param], + queryFn: ({ pageParam = 1 }) => + getMailDomainMailboxes({ ...param, page: pageParam }), + getNextPageParam: (lastPage): number | undefined => { + if (!lastPage.next) { + return undefined; + } + try { + const url = new URL(lastPage.next, window.location.origin); + const nextPage = url.searchParams.get('page'); + return nextPage ? Number(nextPage) : undefined; + } catch { + return undefined; + } + }, + initialPageParam: 1, + ...queryConfig, + }); +} diff --git a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/panel/MailBoxesListView.tsx b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/panel/MailBoxesListView.tsx index 29bdcf8..23da8fd 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/panel/MailBoxesListView.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/mailboxes/components/panel/MailBoxesListView.tsx @@ -1,5 +1,6 @@ -import { DataGrid, SortModel, usePagination } from '@openfun/cunningham-react'; -import { useMemo, useState } from 'react'; +import { DataGrid, SortModel } from '@openfun/cunningham-react'; +import type { InfiniteData } from '@tanstack/react-query'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, Tag, Text, TextErrors } from '@/components'; @@ -9,11 +10,17 @@ import { MailDomainMailboxStatus, } from '@/features/mail-domains/mailboxes/types'; -import { PAGE_SIZE } from '../../../conf'; -import { useMailboxes } from '../../api/useMailboxes'; +import { useMailboxesInfinite } from '../../api/useMailboxesInfinite'; import { PanelActions } from './PanelActions'; +type MailDomainMailboxesResponse = { + count: number; + next: string | null; + previous: string | null; + results: MailDomainMailbox[]; +}; + interface MailBoxesListViewProps { mailDomain: MailDomain; querySearch: string; @@ -43,101 +50,133 @@ export function MailBoxesListView({ const [sortModel] = useState([]); - const pagination = usePagination({ - defaultPage: 1, - pageSize: PAGE_SIZE, - }); - - const { page } = pagination; - const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined; - const { data, isLoading, error } = useMailboxes({ + const { + data, + isLoading, + error, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useMailboxesInfinite({ mailDomainSlug: mailDomain.slug, - page, ordering, - }); + }) as { + data: InfiniteData | undefined; + isLoading: boolean; + error: { cause?: string[] }; + fetchNextPage: () => void; + hasNextPage: boolean | undefined; + isFetchingNextPage: boolean; + }; const mailboxes: ViewMailbox[] = useMemo(() => { - if (!mailDomain || !data?.results?.length) { + if (!mailDomain || !data?.pages?.length) { return []; } - - return data.results.map((mailbox: MailDomainMailbox) => ({ - email: `${mailbox.local_part}@${mailDomain.name}`, - name: `${mailbox.first_name} ${mailbox.last_name}`, - id: mailbox.id, - status: mailbox.status, - mailbox, - })); - }, [data?.results, mailDomain]); + return data.pages.flatMap((page) => + page.results.map((mailbox: MailDomainMailbox) => ({ + email: `${mailbox.local_part}@${mailDomain.name}`, + name: `${mailbox.first_name} ${mailbox.last_name}`, + id: mailbox.id, + status: mailbox.status, + mailbox, + })), + ); + }, [data, mailDomain]); const filteredMailboxes = useMemo(() => { if (!querySearch) { return mailboxes; } const lowerCaseSearch = querySearch.toLowerCase(); - return ( - (mailboxes && - mailboxes.filter((mailbox) => - mailbox.email.toLowerCase().includes(lowerCaseSearch), - )) || - [] + return mailboxes.filter((mailbox) => + mailbox.email.toLowerCase().includes(lowerCaseSearch), ); }, [querySearch, mailboxes]); + const loadMoreRef = useRef(null); + + useEffect(() => { + if (!hasNextPage) { + return; + } + const ref = loadMoreRef.current; + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, + { threshold: 1 }, + ); + if (ref) { + observer.observe(ref); + } + return () => { + if (ref) { + observer.unobserve(ref); + } + }; + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + return (
- {error && } + {error && } {filteredMailboxes && filteredMailboxes.length ? ( - {row.email}, - }, - { - field: 'name', - headerName: t('User'), - enableSorting: true, - renderCell: ({ row }) => ( - - {row.name} - - ), - }, - { - id: 'status', - headerName: t('Status'), - enableSorting: true, - renderCell({ row }) { - return ( - - - - ); + <> + {row.email}, }, - }, - { - id: 'actions', - renderCell: ({ row }) => ( - - ), - }, - ]} - isLoading={isLoading} - /> + { + field: 'name', + headerName: t('User'), + enableSorting: true, + renderCell: ({ row }) => ( + + {row.name} + + ), + }, + { + id: 'status', + headerName: t('Status'), + enableSorting: true, + renderCell({ row }) { + return ( + + + + ); + }, + }, + { + id: 'actions', + renderCell: ({ row }) => ( + + ), + }, + ]} + isLoading={isLoading} + /> +
+ {isFetchingNextPage &&
{t('Loading more...')}
} + ) : null}
); diff --git a/src/frontend/apps/desk/src/pages/mail-domains/index.tsx b/src/frontend/apps/desk/src/pages/mail-domains/index.tsx index c74a413..7ceb804 100644 --- a/src/frontend/apps/desk/src/pages/mail-domains/index.tsx +++ b/src/frontend/apps/desk/src/pages/mail-domains/index.tsx @@ -63,7 +63,7 @@ const Page: NextPageWithLayout = () => { $gap="1em" $css="margin-bottom: 20px;" > - +