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:
parent
f16f9d2403
commit
bc62796e5c
15 changed files with 182 additions and 212 deletions
|
@ -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}
|
||||
|
|
|
@ -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 />} />}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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 */
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -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: (
|
||||
|
|
|
@ -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: (
|
||||
|
|
|
@ -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: (
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
export const buildUrl = (path: string, searchParameters: Record<string, string>) =>
|
||||
`${path}?${new URLSearchParams(searchParameters).toString()}`;
|
||||
|
||||
export const formatSearchKeyword = (keyword: string) => `%${keyword}%`;
|
||||
|
|
Loading…
Reference in a new issue