0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(console): replace useTableSearchParams with useSearchParametersWatcher (#3027)

This commit is contained in:
Xiao Yijun 2023-02-01 12:40:20 +08:00 committed by GitHub
parent f16f9d2403
commit bc62796e5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 182 additions and 212 deletions

View file

@ -9,7 +9,7 @@ import ApplicationName from '@/components/ApplicationName';
import UserName from '@/components/UserName';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import usePageSearchParameters from '@/hooks/use-page-search-parameters';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl } from '@/utilities/url';
import Table from '../Table';
@ -28,7 +28,7 @@ const AuditLogTable = ({ userId, className }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { pathname } = useLocation();
const pageSize = defaultPageSize;
const [{ page, event, applicationId }, updatePageSearchParameters] = usePageSearchParameters({
const [{ page, event, applicationId }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
event: '',
applicationId: '',
@ -103,7 +103,7 @@ const AuditLogTable = ({ userId, className }: Props) => {
<EventSelector
value={event}
onChange={(event) => {
updatePageSearchParameters({ event, page: undefined });
updateSearchParameters({ event, page: undefined });
}}
/>
</div>
@ -111,18 +111,18 @@ const AuditLogTable = ({ userId, className }: Props) => {
<ApplicationSelector
value={applicationId}
onChange={(applicationId) => {
updatePageSearchParameters({ applicationId, page: undefined });
updateSearchParameters({ applicationId, page: undefined });
}}
/>
</div>
</div>
}
pagination={{
pageIndex: Number(page),
page,
totalCount,
pageSize,
onChange: (page) => {
updatePageSearchParameters({ page });
updateSearchParameters({ page });
},
}}
isLoading={isLoading}

View file

@ -11,7 +11,7 @@ import Previous from './Previous';
import * as styles from './index.module.scss';
export type Props = {
pageIndex: number;
page: number;
totalCount?: number;
pageSize: number;
className?: string;
@ -20,7 +20,7 @@ export type Props = {
};
const Pagination = ({
pageIndex,
page,
totalCount,
pageSize,
className,
@ -42,8 +42,8 @@ const Pagination = ({
return null;
}
const min = (pageIndex - 1) * pageSize + 1;
const max = Math.min(pageIndex * pageSize, cachedTotalCount);
const min = (page - 1) * pageSize + 1;
const max = Math.min(page * pageSize, cachedTotalCount);
const isPicoMode = mode === 'pico';
return (
@ -54,13 +54,13 @@ const Pagination = ({
<ReactPaginate
className={styles.pagination}
pageCount={pageCount}
forcePage={pageIndex - 1}
pageLabelBuilder={(page: number) => (
forcePage={page - 1}
pageLabelBuilder={(pageNumber: number) => (
<Button
type={page === pageIndex ? 'outline' : 'default'}
className={classNames(styles.button, page === pageIndex && styles.active)}
type={pageNumber === page ? 'outline' : 'default'}
className={classNames(styles.button, pageNumber === page && styles.active)}
size="small"
title={<DangerousRaw>{page}</DangerousRaw>}
title={<DangerousRaw>{pageNumber}</DangerousRaw>}
/>
)}
previousLabel={<Button className={styles.button} size="small" icon={<Previous />} />}

View file

@ -13,9 +13,8 @@ import TextInput from '@/components/TextInput';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useDebounce from '@/hooks/use-debounce';
import { formatKeyword } from '@/hooks/use-table-search-params';
import * as transferLayout from '@/scss/transfer.module.scss';
import { buildUrl } from '@/utilities/url';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import SourceUserItem from '../SourceUserItem';
import * as styles from './index.module.scss';
@ -26,20 +25,21 @@ type Props = {
selectedUsers: User[];
};
const pageSize = defaultPageSize;
const searchDelay = 500;
const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [pageIndex, setPageIndex] = useState(1);
const [page, setPage] = useState(1);
const [keyword, setKeyword] = useState('');
const debounce = useDebounce();
const url = buildUrl('/api/users', {
excludeRoleId: roleId,
hideAdminUser: String(true),
page: String(pageIndex),
page_size: String(defaultPageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
});
const { data, error } = useSWR<[User[], number], RequestError>(url);
@ -50,7 +50,7 @@ const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
debounce(() => {
setPageIndex(1);
setPage(1);
setKeyword(event.target.value);
}, searchDelay);
};
@ -97,12 +97,12 @@ const SourceUsersBox = ({ roleId, selectedUsers, onChange }: Props) => {
</div>
<Pagination
mode="pico"
pageIndex={pageIndex}
page={page}
totalCount={totalCount}
pageSize={defaultPageSize}
pageSize={pageSize}
className={transferLayout.boxPagination}
onChange={(page) => {
setPageIndex(page);
setPage(page);
}}
/>
</div>

View file

@ -10,6 +10,7 @@ import Search from '@/assets/images/search.svg';
import DataEmpty from '@/components/DataEmpty';
import Pagination from '@/components/Pagination';
import TextInput from '@/components/TextInput';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useDebounce from '@/hooks/use-debounce';
import * as transferLayout from '@/scss/transfer.module.scss';
@ -24,20 +25,20 @@ type Props = {
onChange: (value: RoleResponse[]) => void;
};
const pageSize = 20;
const pageSize = defaultPageSize;
const searchDelay = 500;
const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [pageIndex, setPageIndex] = useState(1);
const [page, setPage] = useState(1);
const [keyword, setKeyword] = useState('');
const debounce = useDebounce();
const url = buildUrl('/api/roles', {
excludeUserId: userId,
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: `%${keyword}%` }),
});
@ -53,7 +54,7 @@ const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
const handleSearchInput = (event: ChangeEvent<HTMLInputElement>) => {
debounce(() => {
setPageIndex(1);
setPage(1);
setKeyword(event.target.value);
}, searchDelay);
};
@ -98,12 +99,12 @@ const SourceRolesBox = ({ userId, selectedRoles, onChange }: Props) => {
</div>
<Pagination
mode="pico"
pageIndex={pageIndex}
page={page}
totalCount={totalCount}
pageSize={pageSize}
className={transferLayout.boxPagination}
onChange={(page) => {
setPageIndex(page);
setPage(page);
}}
/>
</div>

View file

@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom';
type Parameters = Record<string, string | number>;
type UsePageSearchParametersReturn<T extends Parameters = Parameters> = [
type UseSearchParametersWatcherReturn<T extends Parameters = Parameters> = [
{
[K in keyof T]: T[K];
},
@ -12,17 +12,17 @@ type UsePageSearchParametersReturn<T extends Parameters = Parameters> = [
];
/**
* Manage page search parameters
* Watch search parameters
*
* @param config Define search parameter keys and their default value. E.g., `{ page: 1, keyword: '' }`
* @returns [pageSearchParams, updatePageSearchParams]
* @returns [searchParams, updateSearchParams]
*/
const usePageSearchParameters = <T extends Parameters>(
const useSearchParametersWatcher = <T extends Parameters>(
config: T
): UsePageSearchParametersReturn<T> => {
): UseSearchParametersWatcherReturn<T> => {
const [searchParameters, setSearchParameters] = useSearchParams();
const updatePageSearchParameters = useCallback(
const updateSearchParameters = useCallback(
(parameters: Partial<T>) => {
const baseParameters = new URLSearchParams(searchParameters);
@ -54,9 +54,9 @@ const usePageSearchParameters = <T extends Parameters>(
return [parameterKey, parameterValue];
})
) as UsePageSearchParametersReturn<T>[0],
updatePageSearchParameters,
) as UseSearchParametersWatcherReturn<T>[0],
updateSearchParameters,
];
};
export default usePageSearchParameters;
export default useSearchParametersWatcher;

View file

@ -1,62 +0,0 @@
/* 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

@ -8,14 +8,17 @@ import useSWR from 'swr';
import ConfirmModal from '@/components/ConfirmModal';
import PermissionsTable from '@/components/PermissionsTable';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
import { buildUrl } from '@/utilities/url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import type { ApiResourceDetailsOutletContext } from '../types';
import CreatePermissionModal from './components/CreatePermissionModal';
const pageSize = defaultPageSize;
const ApiResourcePermissions = () => {
const {
resource: { id: resourceId },
@ -24,17 +27,17 @@ const ApiResourcePermissions = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
keyword: '',
});
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
resourceId &&
buildUrl(`/api/resources/${resourceId}/scopes`, {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
})
);
@ -77,20 +80,26 @@ const ApiResourcePermissions = () => {
errorMessage={error?.body?.message ?? error?.message}
retryHandler={async () => mutate(undefined, true)}
pagination={{
pageIndex,
page,
pageSize,
totalCount,
onChange: setPageIndex,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
search={{
keyword,
searchHandler: (value) => {
setKeyword(value);
setPageIndex(1);
searchHandler: (keyword) => {
updateSearchParameters({
keyword,
page: 1,
});
},
clearSearchHandler: () => {
setKeyword('');
setPageIndex(1);
updateSearchParameters({
keyword: '',
page: 1,
});
},
}}
/>

View file

@ -3,7 +3,7 @@ import { AppearanceMode } from '@logto/schemas';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import useSWR from 'swr';
import ApiResourceDark from '@/assets/images/api-resource-dark.svg';
@ -15,8 +15,10 @@ import CopyToClipboard from '@/components/CopyToClipboard';
import ItemPreview from '@/components/ItemPreview';
import Pagination from '@/components/Pagination';
import Table from '@/components/Table';
import { defaultPageSize } from '@/consts';
import { ApiResourceDetailsTabs } from '@/consts/page-tabs';
import type { RequestError } from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { useTheme } from '@/hooks/use-theme';
import * as modalStyles from '@/scss/modal.module.scss';
import * as resourcesStyles from '@/scss/resources.module.scss';
@ -25,23 +27,23 @@ import { buildUrl } from '@/utilities/url';
import CreateForm from './components/CreateForm';
import * as styles from './index.module.scss';
const pageSize = defaultPageSize;
const apiResourcesPathname = '/api-resources';
const createApiResourcePathname = `${apiResourcesPathname}/create`;
const buildDetailsPathname = (id: string) =>
`${apiResourcesPathname}/${id}/${ApiResourceDetailsTabs.Settings}`;
const pageSize = 20;
const ApiResources = () => {
const { pathname } = useLocation();
const { pathname, search } = useLocation();
const isCreateNew = pathname.endsWith('/create');
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [query, setQuery] = useSearchParams();
const search = query.toString();
const pageIndex = Number(query.get('page') ?? '1');
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});
const url = buildUrl('/api/resources', {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
});
@ -146,12 +148,12 @@ const ApiResources = () => {
onRetry={async () => mutate(undefined, true)}
/>
<Pagination
pageIndex={pageIndex}
page={page}
totalCount={totalCount}
pageSize={pageSize}
className={styles.pagination}
onChange={(page) => {
setQuery({ page: String(page) });
updateSearchParameters({ page });
}}
/>
</div>

View file

@ -2,7 +2,7 @@ import type { Application } from '@logto/schemas';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import useSWR from 'swr';
import Plus from '@/assets/images/plus.svg';
@ -13,7 +13,9 @@ import CopyToClipboard from '@/components/CopyToClipboard';
import ItemPreview from '@/components/ItemPreview';
import Pagination from '@/components/Pagination';
import Table from '@/components/Table';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import * as modalStyles from '@/scss/modal.module.scss';
import * as resourcesStyles from '@/scss/resources.module.scss';
import { applicationTypeI18nKey } from '@/types/applications';
@ -22,22 +24,23 @@ import { buildUrl } from '@/utilities/url';
import CreateForm from './components/CreateForm';
import * as styles from './index.module.scss';
const pageSize = 20;
const pageSize = defaultPageSize;
const applicationsPathname = '/applications';
const createApplicationPathname = `${applicationsPathname}/create`;
const buildDetailsPathname = (id: string) => `${applicationsPathname}/${id}`;
const Applications = () => {
const navigate = useNavigate();
const { pathname } = useLocation();
const { pathname, search } = useLocation();
const isCreateNew = pathname === createApplicationPathname;
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [query, setQuery] = useSearchParams();
const search = query.toString();
const pageIndex = Number(query.get('page') ?? '1');
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});
const url = buildUrl('/api/applications', {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
});
@ -137,12 +140,12 @@ const Applications = () => {
onRetry={async () => mutate(undefined, true)}
/>
<Pagination
pageIndex={pageIndex}
page={page}
totalCount={totalCount}
pageSize={pageSize}
className={styles.pagination}
onChange={(page) => {
setQuery({ page: String(page) });
updateSearchParameters({ page });
}}
/>
</div>

View file

@ -8,14 +8,17 @@ import useSWR from 'swr';
import ConfirmModal from '@/components/ConfirmModal';
import PermissionsTable from '@/components/PermissionsTable';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
import { buildUrl } from '@/utilities/url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import type { RoleDetailsOutletContext } from '../types';
import AssignPermissionsModal from './components/AssignPermissionsModal';
const pageSize = defaultPageSize;
const RolePermissions = () => {
const {
role: { id: roleId },
@ -23,17 +26,17 @@ const RolePermissions = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
keyword: '',
});
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
roleId &&
buildUrl(`/api/roles/${roleId}/scopes`, {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
})
);
@ -80,20 +83,20 @@ const RolePermissions = () => {
errorMessage={error?.body?.message ?? error?.message}
retryHandler={async () => mutate(undefined, true)}
pagination={{
pageIndex,
page,
pageSize,
totalCount,
onChange: setPageIndex,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
search={{
keyword,
searchHandler: (value) => {
setKeyword(value);
setPageIndex(1);
searchHandler: (keyword) => {
updateSearchParameters({ keyword, page: 1 });
},
clearSearchHandler: () => {
setKeyword('');
setPageIndex(1);
updateSearchParameters({ keyword: '', page: 1 });
},
}}
/>

View file

@ -18,15 +18,18 @@ import Search from '@/components/Search';
import Table from '@/components/Table';
import { Tooltip } from '@/components/Tip';
import UserAvatar from '@/components/UserAvatar';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
import { buildUrl } from '@/utilities/url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import type { RoleDetailsOutletContext } from '../types';
import AssignUsersModal from './components/AssignUsersModal';
import * as styles from './index.module.scss';
const pageSize = defaultPageSize;
const RoleUsers = () => {
const {
role: { id: roleId },
@ -34,17 +37,17 @@ const RoleUsers = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
keyword: '',
});
const { data, error, mutate } = useSWR<[User[], number], RequestError>(
roleId &&
buildUrl(`/api/roles/${roleId}/users`, {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
})
);
@ -132,13 +135,11 @@ const RoleUsers = () => {
defaultValue={keyword}
isClearable={Boolean(keyword)}
placeholder={t('general.search_placeholder')}
onSearch={(value) => {
setKeyword(value);
setPageIndex(1);
onSearch={(keyword) => {
updateSearchParameters({ keyword, page: 1 });
}}
onClearSearch={() => {
setKeyword('');
setPageIndex(1);
updateSearchParameters({ keyword: '', page: 1 });
}}
/>
<Button
@ -153,10 +154,12 @@ const RoleUsers = () => {
</div>
}
pagination={{
pageIndex,
page,
pageSize,
totalCount,
onChange: setPageIndex,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
placeholder={{
content: (

View file

@ -10,10 +10,11 @@ import CardTitle from '@/components/CardTitle';
import ItemPreview from '@/components/ItemPreview';
import Search from '@/components/Search';
import Table from '@/components/Table';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import * as pageStyles from '@/scss/resources.module.scss';
import { buildUrl } from '@/utilities/url';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import AssignedUsers from './components/AssignedUsers';
import CreateRoleModal from './components/CreateRoleModal';
@ -23,21 +24,23 @@ const rolesPathname = '/roles';
const createRolePathname = `${rolesPathname}/create`;
const buildDetailsPathname = (id: string) => `${rolesPathname}/${id}`;
const pageSize = defaultPageSize;
const Roles = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { pathname, search } = useLocation();
const navigate = useNavigate();
const isOnCreatePage = pathname === createRolePathname;
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
keyword: '',
});
const url = buildUrl('/api/roles', {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
});
const { data, error, mutate } = useSWR<[RoleResponse[], number], RequestError>(url);
@ -97,21 +100,21 @@ const Roles = () => {
placeholder={t('roles.search')}
defaultValue={keyword}
isClearable={Boolean(keyword)}
onSearch={(value) => {
setKeyword(value);
setPageIndex(1);
onSearch={(keyword) => {
updateSearchParameters({ keyword, page: 1 });
}}
onClearSearch={() => {
setKeyword('');
setPageIndex(1);
updateSearchParameters({ keyword: '', page: 1 });
}}
/>
}
pagination={{
pageIndex,
page,
totalCount,
pageSize,
onChange: setPageIndex,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
placeholder={{
content: (

View file

@ -15,31 +15,34 @@ import Search from '@/components/Search';
import Table from '@/components/Table';
import TextLink from '@/components/TextLink';
import { Tooltip } from '@/components/Tip';
import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
import { buildUrl } from '@/utilities/url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import type { UserDetailsOutletContext } from '../types';
import AssignRolesModal from './components/AssignRolesModal';
import * as styles from './index.module.scss';
const pageSize = defaultPageSize;
const UserRoles = () => {
const { user } = useOutletContext<UserDetailsOutletContext>();
const { id: userId } = user;
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
keyword: '',
});
const { data, error, mutate } = useSWR<[Role[], number], RequestError>(
buildUrl(`/api/users/${userId}/roles`, {
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: formatKeyword(keyword) }),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
})
);
@ -117,13 +120,11 @@ const UserRoles = () => {
defaultValue={keyword}
isClearable={Boolean(keyword)}
placeholder={t('user_details.roles.search')}
onSearch={(value) => {
setKeyword(value);
setPageIndex(1);
onSearch={(keyword) => {
updateSearchParameters({ keyword, page: 1 });
}}
onClearSearch={() => {
setKeyword('');
setPageIndex(1);
updateSearchParameters({ keyword: '', page: 1 });
}}
/>
<Button
@ -138,10 +139,12 @@ const UserRoles = () => {
</div>
}
pagination={{
pageIndex,
page,
pageSize,
totalCount,
onChange: setPageIndex,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
placeholder={{
content: (

View file

@ -13,15 +13,17 @@ import ItemPreview from '@/components/ItemPreview';
import Search from '@/components/Search';
import Table from '@/components/Table';
import UserAvatar from '@/components/UserAvatar';
import { defaultPageSize } from '@/consts';
import { UserDetailsTabs } from '@/consts/page-tabs';
import type { RequestError } from '@/hooks/use-api';
import useTableSearchParams from '@/hooks/use-table-search-params';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import * as resourcesStyles from '@/scss/resources.module.scss';
import { buildUrl } from '@/utilities/url';
import { buildUrl, formatSearchKeyword } from '@/utilities/url';
import CreateForm from './components/CreateForm';
import * as styles from './index.module.scss';
const pageSize = defaultPageSize;
const usersPathname = '/users';
const createUserPathname = `${usersPathname}/create`;
const buildDetailsPathname = (id: string) => `${usersPathname}/${id}/${UserDetailsTabs.Settings}`;
@ -30,16 +32,17 @@ const Users = () => {
const { pathname, search } = useLocation();
const isCreateNew = pathname === createUserPathname;
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
pagination: { pageIndex, pageSize, setPageIndex },
search: { keyword, setKeyword },
} = useTableSearchParams();
const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
keyword: '',
});
const url = buildUrl('/api/users', {
hideAdminUser: String(true),
page: String(pageIndex),
page: String(page),
page_size: String(pageSize),
...conditional(keyword && { search: `%${keyword}%` }),
...conditional(keyword && { search: formatSearchKeyword(keyword) }),
});
const { data, error, mutate } = useSWR<[User[], number], RequestError>(url);
@ -115,13 +118,11 @@ const Users = () => {
placeholder={t('users.search')}
defaultValue={keyword}
isClearable={Boolean(keyword)}
onSearch={(value) => {
setKeyword(value);
setPageIndex(1);
onSearch={(keyword) => {
updateSearchParameters({ keyword, page: 1 });
}}
onClearSearch={() => {
setKeyword('');
setPageIndex(1);
updateSearchParameters({ keyword: '', page: 1 });
}}
/>
}
@ -143,10 +144,12 @@ const Users = () => {
navigate(buildDetailsPathname(id));
}}
pagination={{
pageIndex,
page,
pageSize,
totalCount,
onChange: setPageIndex,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
onRetry={async () => mutate(undefined, true)}
/>

View file

@ -1,2 +1,4 @@
export const buildUrl = (path: string, searchParameters: Record<string, string>) =>
`${path}?${new URLSearchParams(searchParameters).toString()}`;
export const formatSearchKeyword = (keyword: string) => `%${keyword}%`;