mirror of
https://github.com/sct/overseerr.git
synced 2025-09-29 05:24:44 +02:00
fix(ui): correctly paginate request list
This commit is contained in:
@@ -4,6 +4,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
||||||
|
import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams';
|
||||||
import { useUser } from '../../hooks/useUser';
|
import { useUser } from '../../hooks/useUser';
|
||||||
import globalMessages from '../../i18n/globalMessages';
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
import Button from '../Common/Button';
|
import Button from '../Common/Button';
|
||||||
@@ -42,6 +43,7 @@ const RequestList: React.FC = () => {
|
|||||||
|
|
||||||
const page = router.query.page ? Number(router.query.page) : 1;
|
const page = router.query.page ? Number(router.query.page) : 1;
|
||||||
const pageIndex = page - 1;
|
const pageIndex = page - 1;
|
||||||
|
const updateQueryParams = useUpdateQueryParams({ page: page.toString() });
|
||||||
|
|
||||||
const { data, error, revalidate } = useSWR<RequestResultsResponse>(
|
const { data, error, revalidate } = useSWR<RequestResultsResponse>(
|
||||||
`/api/v1/request?take=${currentPageSize}&skip=${
|
`/api/v1/request?take=${currentPageSize}&skip=${
|
||||||
@@ -284,43 +286,13 @@ const RequestList: React.FC = () => {
|
|||||||
<div className="flex justify-center flex-auto space-x-2 sm:justify-end sm:flex-1">
|
<div className="flex justify-center flex-auto space-x-2 sm:justify-end sm:flex-1">
|
||||||
<Button
|
<Button
|
||||||
disabled={!hasPrevPage}
|
disabled={!hasPrevPage}
|
||||||
onClick={() =>
|
onClick={() => updateQueryParams('page', (page - 1).toString())}
|
||||||
router
|
|
||||||
.push(
|
|
||||||
{
|
|
||||||
pathname: `${router.pathname}?page=${page - 1}`,
|
|
||||||
query: router.query.userId
|
|
||||||
? { userId: router.query.userId }
|
|
||||||
: {},
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => window.scrollTo(0, 0))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage(globalMessages.previous)}
|
{intl.formatMessage(globalMessages.previous)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={!hasNextPage}
|
disabled={!hasNextPage}
|
||||||
onClick={() =>
|
onClick={() => updateQueryParams('page', (page + 1).toString())}
|
||||||
router
|
|
||||||
.push(
|
|
||||||
{
|
|
||||||
pathname: `${router.pathname}?page=${page + 1}`,
|
|
||||||
query: router.query.userId
|
|
||||||
? { userId: router.query.userId }
|
|
||||||
: {},
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => window.scrollTo(0, 0))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage(globalMessages.next)}
|
{intl.formatMessage(globalMessages.next)}
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
LogMessage,
|
LogMessage,
|
||||||
LogsResultsResponse,
|
LogsResultsResponse,
|
||||||
} from '../../../../server/interfaces/api/settingsInterfaces';
|
} from '../../../../server/interfaces/api/settingsInterfaces';
|
||||||
|
import { useUpdateQueryParams } from '../../../hooks/useUpdateQueryParams';
|
||||||
import globalMessages from '../../../i18n/globalMessages';
|
import globalMessages from '../../../i18n/globalMessages';
|
||||||
import Error from '../../../pages/_error';
|
import Error from '../../../pages/_error';
|
||||||
import Badge from '../../Common/Badge';
|
import Badge from '../../Common/Badge';
|
||||||
@@ -53,6 +54,7 @@ const SettingsLogs: React.FC = () => {
|
|||||||
|
|
||||||
const page = router.query.page ? Number(router.query.page) : 1;
|
const page = router.query.page ? Number(router.query.page) : 1;
|
||||||
const pageIndex = page - 1;
|
const pageIndex = page - 1;
|
||||||
|
const updateQueryParams = useUpdateQueryParams({ page: page.toString() });
|
||||||
|
|
||||||
const toggleLogs = () => {
|
const toggleLogs = () => {
|
||||||
setRefreshInterval(refreshInterval === 5000 ? 0 : 5000);
|
setRefreshInterval(refreshInterval === 5000 ? 0 : 5000);
|
||||||
@@ -456,15 +458,7 @@ const SettingsLogs: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
disabled={!hasPrevPage}
|
disabled={!hasPrevPage}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router
|
updateQueryParams('page', (page - 1).toString())
|
||||||
.push(
|
|
||||||
`${router.pathname}?page=${page - 1}`,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => window.scrollTo(0, 0))
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(globalMessages.previous)}
|
{intl.formatMessage(globalMessages.previous)}
|
||||||
@@ -472,15 +466,7 @@ const SettingsLogs: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
disabled={!hasNextPage}
|
disabled={!hasNextPage}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router
|
updateQueryParams('page', (page + 1).toString())
|
||||||
.push(
|
|
||||||
`${router.pathname}?page=${page + 1}`,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => window.scrollTo(0, 0))
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(globalMessages.next)}
|
{intl.formatMessage(globalMessages.next)}
|
||||||
|
@@ -10,6 +10,7 @@ import * as Yup from 'yup';
|
|||||||
import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
||||||
import { hasPermission } from '../../../server/lib/permissions';
|
import { hasPermission } from '../../../server/lib/permissions';
|
||||||
import AddUserIcon from '../../assets/useradd.svg';
|
import AddUserIcon from '../../assets/useradd.svg';
|
||||||
|
import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams';
|
||||||
import { Permission, User, UserType, useUser } from '../../hooks/useUser';
|
import { Permission, User, UserType, useUser } from '../../hooks/useUser';
|
||||||
import globalMessages from '../../i18n/globalMessages';
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
import Alert from '../Common/Alert';
|
import Alert from '../Common/Alert';
|
||||||
@@ -79,6 +80,7 @@ const UserList: React.FC = () => {
|
|||||||
|
|
||||||
const page = router.query.page ? Number(router.query.page) : 1;
|
const page = router.query.page ? Number(router.query.page) : 1;
|
||||||
const pageIndex = page - 1;
|
const pageIndex = page - 1;
|
||||||
|
const updateQueryParams = useUpdateQueryParams({ page: page.toString() });
|
||||||
|
|
||||||
const { data, error, revalidate } = useSWR<UserResultsResponse>(
|
const { data, error, revalidate } = useSWR<UserResultsResponse>(
|
||||||
`/api/v1/user?take=${currentPageSize}&skip=${
|
`/api/v1/user?take=${currentPageSize}&skip=${
|
||||||
@@ -675,15 +677,7 @@ const UserList: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
disabled={!hasPrevPage}
|
disabled={!hasPrevPage}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router
|
updateQueryParams('page', (page - 1).toString())
|
||||||
.push(
|
|
||||||
`${router.pathname}?page=${page - 1}`,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => window.scrollTo(0, 0))
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(globalMessages.previous)}
|
{intl.formatMessage(globalMessages.previous)}
|
||||||
@@ -691,15 +685,7 @@ const UserList: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
disabled={!hasNextPage}
|
disabled={!hasNextPage}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router
|
updateQueryParams('page', (page + 1).toString())
|
||||||
.push(
|
|
||||||
`${router.pathname}?page=${page + 1}`,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
shallow: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => window.scrollTo(0, 0))
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(globalMessages.next)}
|
{intl.formatMessage(globalMessages.next)}
|
||||||
|
133
src/hooks/useUpdateQueryParams.ts
Normal file
133
src/hooks/useUpdateQueryParams.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { NextRouter, useRouter } from 'next/router';
|
||||||
|
import { ParsedUrlQuery } from 'querystring';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
type UseQueryParamReturnedFunction = (
|
||||||
|
query: ParsedUrlQuery,
|
||||||
|
routerAction?: 'push' | 'replace'
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
interface MergedQueryString {
|
||||||
|
pathname: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a filtered object containing only key/value pairs that don't exist in the current
|
||||||
|
* router path.
|
||||||
|
*
|
||||||
|
* @param router Nextjs router instance
|
||||||
|
* @param filters Object containing key value pairs for filter items that should be cleaned
|
||||||
|
*/
|
||||||
|
export const filterQueryString = (
|
||||||
|
router: NextRouter,
|
||||||
|
filters: ParsedUrlQuery
|
||||||
|
): ParsedUrlQuery => {
|
||||||
|
const cleanedFilters: ParsedUrlQuery = {};
|
||||||
|
|
||||||
|
Object.keys(filters).forEach((key) => {
|
||||||
|
if (!router.pathname.match(new RegExp(`${key}`))) {
|
||||||
|
cleanedFilters[key] = filters[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanedFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a query paramter object and returns a new pathname and path
|
||||||
|
* with the new values appended.
|
||||||
|
*
|
||||||
|
* - If the value already exists, it is updated.
|
||||||
|
* - If a key with the value of null is passed, it will be removed from
|
||||||
|
* the current query paramters
|
||||||
|
*
|
||||||
|
* ## Example usage:
|
||||||
|
*
|
||||||
|
* If the current URL is `/foo?bar=test` and you want to add a new query parameter, you
|
||||||
|
* can do the following:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const newRoute = mergeQueryString(router, { newParam: 'value' });
|
||||||
|
* ```
|
||||||
|
* NewRoute will become
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* pathName: '/foo?bar=test&newParam=value',
|
||||||
|
* path: '/foo?bar=test&newParam=value'
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param router Nextjs router instance
|
||||||
|
* @param query Key/value pair object containing query paramters
|
||||||
|
*/
|
||||||
|
export const mergeQueryString = (
|
||||||
|
router: NextRouter,
|
||||||
|
query: ParsedUrlQuery
|
||||||
|
): MergedQueryString => {
|
||||||
|
const cleanedQuery = filterQueryString(router, router.query);
|
||||||
|
|
||||||
|
const mergedQuery = Object.assign({}, cleanedQuery, query);
|
||||||
|
|
||||||
|
const queryArray: string[] = [];
|
||||||
|
|
||||||
|
Object.keys(mergedQuery).map((key) => {
|
||||||
|
if (mergedQuery[key]) {
|
||||||
|
queryArray.push(`${key}=${mergedQuery[key]}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pathWithoutQuery = router.asPath.match(/(.*)\?.*/);
|
||||||
|
const asPath = pathWithoutQuery ? pathWithoutQuery[1] : router.asPath;
|
||||||
|
|
||||||
|
const pathname = `${router.pathname}${
|
||||||
|
queryArray.length > 0 ? `?${queryArray.join('&')}` : ''
|
||||||
|
}`;
|
||||||
|
const path = `${asPath}${
|
||||||
|
queryArray.length > 0 ? `?${queryArray.join('&')}` : ''
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return { pathname, path };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useQueryParams hook is used just to provide a callback with a nextjs
|
||||||
|
* router instance attached to it. The returned method can be called with
|
||||||
|
* an object of key/value pairs to route the user with the new query paramters
|
||||||
|
*/
|
||||||
|
export const useQueryParams = (): UseQueryParamReturnedFunction => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(query: ParsedUrlQuery, routerAction: 'push' | 'replace' = 'push') => {
|
||||||
|
const newRoute = mergeQueryString(router, query);
|
||||||
|
|
||||||
|
if (newRoute.path !== router.asPath) {
|
||||||
|
if (routerAction === 'replace') {
|
||||||
|
router.replace(newRoute.pathname, newRoute.path);
|
||||||
|
} else {
|
||||||
|
router.push(newRoute.pathname, newRoute.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateQueryParams = (
|
||||||
|
filter: ParsedUrlQuery
|
||||||
|
): ((key: string, value?: string) => void) => {
|
||||||
|
const updateQueryParams = useQueryParams();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(key: string, value?: string) => {
|
||||||
|
const query = {
|
||||||
|
...filter,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
updateQueryParams(query, 'replace');
|
||||||
|
},
|
||||||
|
[filter, updateQueryParams]
|
||||||
|
);
|
||||||
|
};
|
Reference in New Issue
Block a user