mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-01 20:28:40 -05:00
this helps return back to the page you were last looking at when navigated to and from the request list.
272 lines
9.7 KiB
TypeScript
272 lines
9.7 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import useSWR from 'swr';
|
|
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
|
import LoadingSpinner from '../Common/LoadingSpinner';
|
|
import RequestItem from './RequestItem';
|
|
import Header from '../Common/Header';
|
|
import Button from '../Common/Button';
|
|
import { defineMessages, useIntl } from 'react-intl';
|
|
import PageTitle from '../Common/PageTitle';
|
|
import { useRouter } from 'next/router';
|
|
|
|
const messages = defineMessages({
|
|
requests: 'Requests',
|
|
showingresults:
|
|
'Showing <strong>{from}</strong> to <strong>{to}</strong> of <strong>{total}</strong> results',
|
|
resultsperpage: 'Display {pageSize} results per page',
|
|
next: 'Next',
|
|
previous: 'Previous',
|
|
filterAll: 'All',
|
|
filterPending: 'Pending',
|
|
filterApproved: 'Approved',
|
|
filterAvailable: 'Available',
|
|
filterProcessing: 'Processing',
|
|
noresults: 'No results.',
|
|
showallrequests: 'Show All Requests',
|
|
sortAdded: 'Request Date',
|
|
sortModified: 'Last Modified',
|
|
});
|
|
|
|
type Filter = 'all' | 'pending' | 'approved' | 'processing' | 'available';
|
|
type Sort = 'added' | 'modified';
|
|
|
|
const RequestList: React.FC = () => {
|
|
const router = useRouter();
|
|
const intl = useIntl();
|
|
const [currentFilter, setCurrentFilter] = useState<Filter>('pending');
|
|
const [currentSort, setCurrentSort] = useState<Sort>('added');
|
|
const [currentPageSize, setCurrentPageSize] = useState<number>(10);
|
|
|
|
const page = router.query.page ? Number(router.query.page) : 1;
|
|
const pageIndex = page - 1;
|
|
|
|
const { data, error, revalidate } = useSWR<RequestResultsResponse>(
|
|
`/api/v1/request?take=${currentPageSize}&skip=${
|
|
pageIndex * currentPageSize
|
|
}&filter=${currentFilter}&sort=${currentSort}`
|
|
);
|
|
|
|
// Restore last set filter values on component mount
|
|
useEffect(() => {
|
|
const filterString = window.localStorage.getItem('rl-filter-settings');
|
|
|
|
if (filterString) {
|
|
const filterSettings = JSON.parse(filterString);
|
|
|
|
setCurrentFilter(filterSettings.currentFilter);
|
|
setCurrentSort(filterSettings.currentSort);
|
|
setCurrentPageSize(filterSettings.currentPageSize);
|
|
}
|
|
}, []);
|
|
|
|
// Set fitler values to local storage any time they are changed
|
|
useEffect(() => {
|
|
window.localStorage.setItem(
|
|
'rl-filter-settings',
|
|
JSON.stringify({
|
|
currentFilter,
|
|
currentSort,
|
|
currentPageSize,
|
|
})
|
|
);
|
|
}, [currentFilter, currentSort, currentPageSize]);
|
|
|
|
if (!data && !error) {
|
|
return <LoadingSpinner />;
|
|
}
|
|
|
|
if (!data) {
|
|
return <LoadingSpinner />;
|
|
}
|
|
|
|
const hasNextPage = data.pageInfo.pages > pageIndex + 1;
|
|
const hasPrevPage = pageIndex > 0;
|
|
|
|
return (
|
|
<>
|
|
<PageTitle title={intl.formatMessage(messages.requests)} />
|
|
<div className="flex flex-col justify-between mb-4 lg:items-end lg:flex-row">
|
|
<Header>{intl.formatMessage(messages.requests)}</Header>
|
|
<div className="flex flex-col flex-grow mt-2 sm:flex-row lg:flex-grow-0">
|
|
<div className="flex flex-grow mb-2 sm:mb-0 sm:mr-2 lg:flex-grow-0">
|
|
<span className="inline-flex items-center px-3 text-sm text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md">
|
|
<svg
|
|
className="w-6 h-6"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
<select
|
|
id="filter"
|
|
name="filter"
|
|
onChange={(e) => {
|
|
setCurrentFilter(e.target.value as Filter);
|
|
router.push(router.pathname);
|
|
}}
|
|
value={currentFilter}
|
|
className="rounded-r-only"
|
|
>
|
|
<option value="all">
|
|
{intl.formatMessage(messages.filterAll)}
|
|
</option>
|
|
<option value="pending">
|
|
{intl.formatMessage(messages.filterPending)}
|
|
</option>
|
|
<option value="approved">
|
|
{intl.formatMessage(messages.filterApproved)}
|
|
</option>
|
|
<option value="processing">
|
|
{intl.formatMessage(messages.filterProcessing)}
|
|
</option>
|
|
<option value="available">
|
|
{intl.formatMessage(messages.filterAvailable)}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div className="flex flex-grow mb-2 sm:mb-0 lg:flex-grow-0">
|
|
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default sm:text-sm rounded-l-md">
|
|
<svg
|
|
className="w-6 h-6"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path d="M3 3a1 1 0 000 2h11a1 1 0 100-2H3zM3 7a1 1 0 000 2h7a1 1 0 100-2H3zM3 11a1 1 0 100 2h4a1 1 0 100-2H3zM15 8a1 1 0 10-2 0v5.586l-1.293-1.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L15 13.586V8z" />
|
|
</svg>
|
|
</span>
|
|
<select
|
|
id="sort"
|
|
name="sort"
|
|
onChange={(e) => {
|
|
setCurrentSort(e.target.value as Sort);
|
|
router.push(router.pathname);
|
|
}}
|
|
value={currentSort}
|
|
className="rounded-r-only"
|
|
>
|
|
<option value="added">
|
|
{intl.formatMessage(messages.sortAdded)}
|
|
</option>
|
|
<option value="modified">
|
|
{intl.formatMessage(messages.sortModified)}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{data.results.map((request) => {
|
|
return (
|
|
<div className="py-2" key={`request-list-${request.id}`}>
|
|
<RequestItem
|
|
request={request}
|
|
revalidateList={() => revalidate()}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{data.results.length === 0 && (
|
|
<div className="flex flex-col items-center justify-center w-full py-24 text-white">
|
|
<span className="text-2xl text-gray-400">
|
|
{intl.formatMessage(messages.noresults)}
|
|
</span>
|
|
{currentFilter !== 'all' && (
|
|
<div className="mt-4">
|
|
<Button
|
|
buttonType="primary"
|
|
onClick={() => setCurrentFilter('all')}
|
|
>
|
|
{intl.formatMessage(messages.showallrequests)}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="actions">
|
|
<nav
|
|
className="flex flex-col items-center mb-3 space-y-3 sm:space-y-0 sm:flex-row"
|
|
aria-label="Pagination"
|
|
>
|
|
<div className="hidden lg:flex lg:flex-1">
|
|
<p className="text-sm">
|
|
{data.results.length > 0 &&
|
|
intl.formatMessage(messages.showingresults, {
|
|
from: pageIndex * currentPageSize + 1,
|
|
to:
|
|
data.results.length < currentPageSize
|
|
? pageIndex * currentPageSize + data.results.length
|
|
: (pageIndex + 1) * currentPageSize,
|
|
total: data.pageInfo.results,
|
|
strong: function strong(msg) {
|
|
return <span className="font-medium">{msg}</span>;
|
|
},
|
|
})}
|
|
</p>
|
|
</div>
|
|
<div className="flex justify-center sm:flex-1 sm:justify-start lg:justify-center">
|
|
<span className="items-center -mt-3 text-sm truncate sm:mt-0">
|
|
{intl.formatMessage(messages.resultsperpage, {
|
|
pageSize: (
|
|
<select
|
|
id="pageSize"
|
|
name="pageSize"
|
|
onChange={(e) => {
|
|
setCurrentPageSize(Number(e.target.value));
|
|
router
|
|
.push(router.pathname)
|
|
.then(() => window.scrollTo(0, 0));
|
|
}}
|
|
value={currentPageSize}
|
|
className="inline short"
|
|
>
|
|
<option value="5">5</option>
|
|
<option value="10">10</option>
|
|
<option value="25">25</option>
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
</select>
|
|
),
|
|
})}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-center flex-auto space-x-2 sm:justify-end sm:flex-1">
|
|
<Button
|
|
disabled={!hasPrevPage}
|
|
onClick={() =>
|
|
router
|
|
.push(`${router.pathname}?page=${page - 1}`, undefined, {
|
|
shallow: true,
|
|
})
|
|
.then(() => window.scrollTo(0, 0))
|
|
}
|
|
>
|
|
{intl.formatMessage(messages.previous)}
|
|
</Button>
|
|
<Button
|
|
disabled={!hasNextPage}
|
|
onClick={() =>
|
|
router
|
|
.push(`${router.pathname}?page=${page + 1}`, undefined, {
|
|
shallow: true,
|
|
})
|
|
.then(() => window.scrollTo(0, 0))
|
|
}
|
|
>
|
|
{intl.formatMessage(messages.next)}
|
|
</Button>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default RequestList;
|