🐛(front) fix missing pagination mail domains (#946)
fix missing pagination mail domains + mailboxes list
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<HTMLDivElement | null>(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 (
|
||||
<div role="listbox">
|
||||
{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}
|
||||
<div ref={loadMoreRef} style={{ height: 32 }} />
|
||||
{isFetchingNextPage && <div>{t('Loading more...')}</div>}
|
||||
{!filteredMailDomains ||
|
||||
(!filteredMailDomains.length && (
|
||||
<Text $align="center" $size="small">
|
||||
|
||||
@@ -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<MailDomainMailbox>;
|
||||
|
||||
export function useMailboxesInfinite(
|
||||
param: Omit<MailDomainMailboxesParams, 'page'>,
|
||||
queryConfig = {},
|
||||
) {
|
||||
return useInfiniteQuery<
|
||||
MailDomainMailboxesResponse,
|
||||
APIError,
|
||||
MailDomainMailboxesResponse,
|
||||
[string, Omit<MailDomainMailboxesParams, 'page'>],
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -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<SortModel>([]);
|
||||
|
||||
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<MailDomainMailboxesResponse, number> | 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<HTMLDivElement | null>(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 (
|
||||
<div>
|
||||
{error && <TextErrors causes={error.cause} />}
|
||||
{error && <TextErrors causes={error.cause ?? []} />}
|
||||
|
||||
{filteredMailboxes && filteredMailboxes.length ? (
|
||||
<DataGrid
|
||||
aria-label="listbox"
|
||||
rows={filteredMailboxes}
|
||||
columns={[
|
||||
{
|
||||
field: 'email',
|
||||
headerName: `${t('Address')} • ${filteredMailboxes.length}`,
|
||||
renderCell: ({ row }) => <Text>{row.email}</Text>,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
headerName: t('User'),
|
||||
enableSorting: true,
|
||||
renderCell: ({ row }) => (
|
||||
<Text
|
||||
$weight="500"
|
||||
$theme="greyscale"
|
||||
$css="text-transform: capitalize;"
|
||||
>
|
||||
{row.name}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
headerName: t('Status'),
|
||||
enableSorting: true,
|
||||
renderCell({ row }) {
|
||||
return (
|
||||
<Box $direction="row" $align="center">
|
||||
<Tag
|
||||
showTooltip={true}
|
||||
status={row.status}
|
||||
tooltipType="mail"
|
||||
></Tag>
|
||||
</Box>
|
||||
);
|
||||
<>
|
||||
<DataGrid
|
||||
aria-label="listbox"
|
||||
rows={filteredMailboxes}
|
||||
columns={[
|
||||
{
|
||||
field: 'email',
|
||||
headerName: `${t('Address')} • ${filteredMailboxes.length}`,
|
||||
renderCell: ({ row }) => <Text>{row.email}</Text>,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
renderCell: ({ row }) => (
|
||||
<PanelActions mailDomain={mailDomain} mailbox={row} />
|
||||
),
|
||||
},
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
{
|
||||
field: 'name',
|
||||
headerName: t('User'),
|
||||
enableSorting: true,
|
||||
renderCell: ({ row }) => (
|
||||
<Text
|
||||
$weight="500"
|
||||
$theme="greyscale"
|
||||
$css="text-transform: capitalize;"
|
||||
>
|
||||
{row.name}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
headerName: t('Status'),
|
||||
enableSorting: true,
|
||||
renderCell({ row }) {
|
||||
return (
|
||||
<Box $direction="row" $align="center">
|
||||
<Tag
|
||||
showTooltip={true}
|
||||
status={row.status}
|
||||
tooltipType="mail"
|
||||
></Tag>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
renderCell: ({ row }) => (
|
||||
<PanelActions mailDomain={mailDomain} mailbox={row} />
|
||||
),
|
||||
},
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<div ref={loadMoreRef} style={{ height: 32 }} />
|
||||
{isFetchingNextPage && <div>{t('Loading more...')}</div>}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ const Page: NextPageWithLayout = () => {
|
||||
$gap="1em"
|
||||
$css="margin-bottom: 20px;"
|
||||
>
|
||||
<Box $css="width: calc(100% - 245px);" $flex="1">
|
||||
<Box $flex="1">
|
||||
<Input
|
||||
style={{ width: '100%' }}
|
||||
label={t('Search a mail domain')}
|
||||
|
||||
Reference in New Issue
Block a user