0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

refactor(console): support pagination and search on the roles page (#2908)

This commit is contained in:
Xiao Yijun 2023-01-11 15:42:32 +08:00 committed by GitHub
parent 64d2fa5a63
commit 701bd1b16d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 204 additions and 83 deletions

View file

@ -10,7 +10,7 @@ import Next from './Next';
import Previous from './Previous';
import * as styles from './index.module.scss';
type Props = {
export type Props = {
pageIndex: number;
totalCount?: number;
pageSize: number;

View file

@ -1,9 +1,16 @@
@use '@/scss/underscore' as _;
.container {
display: flex;
flex-direction: column;
overflow: hidden;
}
.tableContainer {
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
.filterContainer {
background-color: var(--color-layer-1);
@ -88,3 +95,7 @@
}
}
}
.pagination {
margin-top: _.unit(4);
}

View file

@ -4,6 +4,9 @@ import type { ReactNode } from 'react';
import { Fragment } from 'react';
import type { FieldPath, FieldValues } from 'react-hook-form';
import type { Props as PaginationProps } from '@/components/Pagination';
import Pagination from '@/components/Pagination';
import TableEmpty from './TableEmpty';
import TableError from './TableError';
import TableLoading from './TableLoading';
@ -31,6 +34,7 @@ type Props<
headerClassName?: string;
bodyClassName?: string;
isLoading?: boolean;
pagination?: PaginationProps;
placeholder?: TablePlaceholder;
errorMessage?: string;
onRetry?: () => void;
@ -50,6 +54,7 @@ const Table = <
headerClassName,
bodyClassName,
isLoading,
pagination,
placeholder,
errorMessage,
onRetry,
@ -62,6 +67,7 @@ const Table = <
return (
<div className={classNames(styles.container, className)}>
<div className={styles.tableContainer}>
{filter && (
<div className={styles.filterContainer}>
<div className={styles.filter}>{filter}</div>
@ -141,6 +147,8 @@ const Table = <
</table>
</div>
</div>
{pagination && <Pagination className={styles.pagination} {...pagination} />}
</div>
);
};

View file

@ -4,3 +4,4 @@ export * from './logs';
export const themeStorageKey = 'logto:admin_console:theme';
export const requestTimeout = 20_000;
export const defaultPageSize = 20;

View file

@ -0,0 +1,62 @@
/* eslint-disable unicorn/prevent-abbreviations */
import type { Optional } from '@silverhand/essentials';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { defaultPageSize } from '@/consts';
type Props = Optional<{
pageSize?: number;
}>;
const pageIndexKey = 'page';
const keywordKey = 'keyword';
export const formatKeyword = (keyword: string) => `%${keyword}%`;
const useTableSearchParams = ({ pageSize = defaultPageSize }: Props = {}) => {
const [searchParams, setSearchParams] = useSearchParams();
const [searchParamsState, setSearchParamsState] = useState<URLSearchParams>(searchParams);
useEffect(() => {
setSearchParams(searchParamsState);
}, [searchParamsState, setSearchParams]);
const setPageIndex = (pageIndex: number) => {
setSearchParamsState((previousParams) => {
const params = new URLSearchParams(previousParams);
params.set(pageIndexKey, String(pageIndex));
return params;
});
};
const setKeyword = (value: string) => {
setSearchParamsState((previousParams) => {
const params = new URLSearchParams(previousParams);
if (value) {
params.set(keywordKey, value);
} else {
params.delete(keywordKey);
}
return params;
});
};
return {
pagination: {
pageIndex: Number(searchParamsState.get(pageIndexKey) ?? 1),
pageSize,
setPageIndex,
},
search: {
keyword: searchParamsState.get(keywordKey) ?? '',
setKeyword,
},
};
};
export default useTableSearchParams;
/* eslint-enable unicorn/prevent-abbreviations */

View file

@ -1,4 +1,5 @@
import type { Role } from '@logto/schemas';
import type { RoleResponse } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import useSWR from 'swr';
@ -7,9 +8,12 @@ import Plus from '@/assets/images/plus.svg';
import Button from '@/components/Button';
import CardTitle from '@/components/CardTitle';
import ItemPreview from '@/components/ItemPreview';
import Search from '@/components/Search';
import Table from '@/components/Table';
import type { RequestError } from '@/hooks/use-api';
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
import * as pageStyles from '@/scss/resources.module.scss';
import { buildUrl } from '@/utilities/url';
import CreateRoleModal from './components/CreateRoleModal';
@ -19,11 +23,26 @@ const buildDetailsPathname = (id: string) => `${rolesPathname}/${id}`;
const Roles = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { pathname } = useLocation();
const { pathname, search } = useLocation();
const navigate = useNavigate();
const isOnCreatePage = pathname === createRolePathname;
const { data: roles, error, mutate } = useSWR<Role[], RequestError>(`/api/roles`);
const isLoading = !roles && !error;
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const url = buildUrl('/api/roles', {
page: String(pageIndex),
page_size: String(pageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
});
const { data, error, mutate } = useSWR<[RoleResponse[], number], RequestError>(url);
const isLoading = !data && !error;
const [roles, totalCount] = data ?? [];
return (
<div className={pageStyles.container}>
@ -35,7 +54,7 @@ const Roles = () => {
type="primary"
size="large"
onClick={() => {
navigate(createRolePathname);
navigate({ pathname: createRolePathname, search });
}}
/>
</div>
@ -62,13 +81,33 @@ const Roles = () => {
rowClickHandler={({ id }) => {
navigate(buildDetailsPathname(id));
}}
filter={
<Search
defaultValue={keyword}
isClearable={Boolean(keyword)}
onSearch={(value) => {
setKeyword(value);
setPageIndex(1);
}}
onClearSearch={() => {
setKeyword('');
setPageIndex(1);
}}
/>
}
pagination={{
pageIndex,
totalCount,
pageSize,
onChange: setPageIndex,
}}
placeholder={{
content: (
<Button
title="roles.create"
type="outline"
onClick={() => {
navigate(createRolePathname);
navigate({ pathname: createRolePathname, search });
}}
/>
),
@ -78,7 +117,7 @@ const Roles = () => {
{isOnCreatePage && (
<CreateRoleModal
onClose={() => {
navigate(rolesPathname);
navigate({ pathname: rolesPathname, search });
}}
/>
)}