🐛(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
|
### Added
|
||||||
|
|
||||||
|
- 🐛(front) fix missing pagination mail domains
|
||||||
- 🐛(front) fix button add mail domain
|
- 🐛(front) fix button add mail domain
|
||||||
- ✨(teams) add matrix webhook for teams #904
|
- ✨(teams) add matrix webhook for teams #904
|
||||||
- ✨(resource-server) add SCIM /Me endpoint #895
|
- ✨(resource-server) add SCIM /Me endpoint #895
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, DataGrid } from '@openfun/cunningham-react';
|
import { Button, DataGrid } from '@openfun/cunningham-react';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, StyledLink, Tag, Text } from '@/components';
|
import { Box, StyledLink, Tag, Text } from '@/components';
|
||||||
@@ -17,7 +17,8 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { ordering } = useMailDomainsStore();
|
const { ordering } = useMailDomainsStore();
|
||||||
const { data, isLoading } = useMailDomains({ ordering });
|
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||||
|
useMailDomains({ ordering });
|
||||||
const mailDomains = useMemo(() => {
|
const mailDomains = useMemo(() => {
|
||||||
return data?.pages.reduce((acc, page) => {
|
return data?.pages.reduce((acc, page) => {
|
||||||
return acc.concat(page.results);
|
return acc.concat(page.results);
|
||||||
@@ -38,6 +39,31 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) {
|
|||||||
);
|
);
|
||||||
}, [querySearch, mailDomains]);
|
}, [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 (
|
return (
|
||||||
<div role="listbox">
|
<div role="listbox">
|
||||||
{filteredMailDomains && filteredMailDomains.length ? (
|
{filteredMailDomains && filteredMailDomains.length ? (
|
||||||
@@ -47,17 +73,17 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) {
|
|||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
headerName: 'Domaine',
|
headerName: `${t('Domain')} (${filteredMailDomains.length})`,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'count_mailboxes',
|
field: 'count_mailboxes',
|
||||||
headerName: "Nombre d'adresses",
|
headerName: `${t('Number of mailboxes')}`,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'status',
|
id: 'status',
|
||||||
headerName: 'Statut',
|
headerName: `${t('Status')}`,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
renderCell({ row }) {
|
renderCell({ row }) {
|
||||||
return (
|
return (
|
||||||
@@ -96,6 +122,8 @@ export function MailDomainsListView({ querySearch }: MailDomainsListViewProps) {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<div ref={loadMoreRef} style={{ height: 32 }} />
|
||||||
|
{isFetchingNextPage && <div>{t('Loading more...')}</div>}
|
||||||
{!filteredMailDomains ||
|
{!filteredMailDomains ||
|
||||||
(!filteredMailDomains.length && (
|
(!filteredMailDomains.length && (
|
||||||
<Text $align="center" $size="small">
|
<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 { DataGrid, SortModel } from '@openfun/cunningham-react';
|
||||||
import { useMemo, useState } from 'react';
|
import type { InfiniteData } from '@tanstack/react-query';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, Tag, Text, TextErrors } from '@/components';
|
import { Box, Tag, Text, TextErrors } from '@/components';
|
||||||
@@ -9,11 +10,17 @@ import {
|
|||||||
MailDomainMailboxStatus,
|
MailDomainMailboxStatus,
|
||||||
} from '@/features/mail-domains/mailboxes/types';
|
} from '@/features/mail-domains/mailboxes/types';
|
||||||
|
|
||||||
import { PAGE_SIZE } from '../../../conf';
|
import { useMailboxesInfinite } from '../../api/useMailboxesInfinite';
|
||||||
import { useMailboxes } from '../../api/useMailboxes';
|
|
||||||
|
|
||||||
import { PanelActions } from './PanelActions';
|
import { PanelActions } from './PanelActions';
|
||||||
|
|
||||||
|
type MailDomainMailboxesResponse = {
|
||||||
|
count: number;
|
||||||
|
next: string | null;
|
||||||
|
previous: string | null;
|
||||||
|
results: MailDomainMailbox[];
|
||||||
|
};
|
||||||
|
|
||||||
interface MailBoxesListViewProps {
|
interface MailBoxesListViewProps {
|
||||||
mailDomain: MailDomain;
|
mailDomain: MailDomain;
|
||||||
querySearch: string;
|
querySearch: string;
|
||||||
@@ -43,101 +50,133 @@ export function MailBoxesListView({
|
|||||||
|
|
||||||
const [sortModel] = useState<SortModel>([]);
|
const [sortModel] = useState<SortModel>([]);
|
||||||
|
|
||||||
const pagination = usePagination({
|
|
||||||
defaultPage: 1,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { page } = pagination;
|
|
||||||
|
|
||||||
const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined;
|
const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined;
|
||||||
const { data, isLoading, error } = useMailboxes({
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetchingNextPage,
|
||||||
|
} = useMailboxesInfinite({
|
||||||
mailDomainSlug: mailDomain.slug,
|
mailDomainSlug: mailDomain.slug,
|
||||||
page,
|
|
||||||
ordering,
|
ordering,
|
||||||
});
|
}) as {
|
||||||
|
data: InfiniteData<MailDomainMailboxesResponse, number> | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
error: { cause?: string[] };
|
||||||
|
fetchNextPage: () => void;
|
||||||
|
hasNextPage: boolean | undefined;
|
||||||
|
isFetchingNextPage: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const mailboxes: ViewMailbox[] = useMemo(() => {
|
const mailboxes: ViewMailbox[] = useMemo(() => {
|
||||||
if (!mailDomain || !data?.results?.length) {
|
if (!mailDomain || !data?.pages?.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
return data.pages.flatMap((page) =>
|
||||||
return data.results.map((mailbox: MailDomainMailbox) => ({
|
page.results.map((mailbox: MailDomainMailbox) => ({
|
||||||
email: `${mailbox.local_part}@${mailDomain.name}`,
|
email: `${mailbox.local_part}@${mailDomain.name}`,
|
||||||
name: `${mailbox.first_name} ${mailbox.last_name}`,
|
name: `${mailbox.first_name} ${mailbox.last_name}`,
|
||||||
id: mailbox.id,
|
id: mailbox.id,
|
||||||
status: mailbox.status,
|
status: mailbox.status,
|
||||||
mailbox,
|
mailbox,
|
||||||
}));
|
})),
|
||||||
}, [data?.results, mailDomain]);
|
);
|
||||||
|
}, [data, mailDomain]);
|
||||||
|
|
||||||
const filteredMailboxes = useMemo(() => {
|
const filteredMailboxes = useMemo(() => {
|
||||||
if (!querySearch) {
|
if (!querySearch) {
|
||||||
return mailboxes;
|
return mailboxes;
|
||||||
}
|
}
|
||||||
const lowerCaseSearch = querySearch.toLowerCase();
|
const lowerCaseSearch = querySearch.toLowerCase();
|
||||||
return (
|
return mailboxes.filter((mailbox) =>
|
||||||
(mailboxes &&
|
mailbox.email.toLowerCase().includes(lowerCaseSearch),
|
||||||
mailboxes.filter((mailbox) =>
|
|
||||||
mailbox.email.toLowerCase().includes(lowerCaseSearch),
|
|
||||||
)) ||
|
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
}, [querySearch, mailboxes]);
|
}, [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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{error && <TextErrors causes={error.cause} />}
|
{error && <TextErrors causes={error.cause ?? []} />}
|
||||||
|
|
||||||
{filteredMailboxes && filteredMailboxes.length ? (
|
{filteredMailboxes && filteredMailboxes.length ? (
|
||||||
<DataGrid
|
<>
|
||||||
aria-label="listbox"
|
<DataGrid
|
||||||
rows={filteredMailboxes}
|
aria-label="listbox"
|
||||||
columns={[
|
rows={filteredMailboxes}
|
||||||
{
|
columns={[
|
||||||
field: 'email',
|
{
|
||||||
headerName: `${t('Address')} • ${filteredMailboxes.length}`,
|
field: 'email',
|
||||||
renderCell: ({ row }) => <Text>{row.email}</Text>,
|
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>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
field: 'name',
|
||||||
id: 'actions',
|
headerName: t('User'),
|
||||||
renderCell: ({ row }) => (
|
enableSorting: true,
|
||||||
<PanelActions mailDomain={mailDomain} mailbox={row} />
|
renderCell: ({ row }) => (
|
||||||
),
|
<Text
|
||||||
},
|
$weight="500"
|
||||||
]}
|
$theme="greyscale"
|
||||||
isLoading={isLoading}
|
$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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const Page: NextPageWithLayout = () => {
|
|||||||
$gap="1em"
|
$gap="1em"
|
||||||
$css="margin-bottom: 20px;"
|
$css="margin-bottom: 20px;"
|
||||||
>
|
>
|
||||||
<Box $css="width: calc(100% - 245px);" $flex="1">
|
<Box $flex="1">
|
||||||
<Input
|
<Input
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
label={t('Search a mail domain')}
|
label={t('Search a mail domain')}
|
||||||
|
|||||||
Reference in New Issue
Block a user