When requested, the request card shows as {seasonCount, plural, one {Season}} and does not display
which season or episode was requested because it was still using the alpha request cards. This fixed
that issue
301 lines
10 KiB
TypeScript
301 lines
10 KiB
TypeScript
import {
|
|
ChevronLeftIcon,
|
|
ChevronRightIcon,
|
|
FilterIcon,
|
|
SortDescendingIcon,
|
|
} from '@heroicons/react/solid';
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/router';
|
|
import React, { useEffect, useState } from 'react';
|
|
import { defineMessages, useIntl } from 'react-intl';
|
|
import useSWR from 'swr';
|
|
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
|
import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams';
|
|
import { useUser } from '../../hooks/useUser';
|
|
import globalMessages from '../../i18n/globalMessages';
|
|
import Button from '../Common/Button';
|
|
import Header from '../Common/Header';
|
|
import LoadingSpinner from '../Common/LoadingSpinner';
|
|
import PageTitle from '../Common/PageTitle';
|
|
import RequestItem from './RequestItem';
|
|
|
|
const messages = defineMessages({
|
|
requests: 'Requests',
|
|
showallrequests: 'Show All Requests',
|
|
sortAdded: 'Most Recent',
|
|
sortModified: 'Last Modified',
|
|
});
|
|
|
|
enum Filter {
|
|
ALL = 'all',
|
|
PENDING = 'pending',
|
|
APPROVED = 'approved',
|
|
PROCESSING = 'processing',
|
|
AVAILABLE = 'available',
|
|
UNAVAILABLE = 'unavailable',
|
|
}
|
|
|
|
type Sort = 'added' | 'modified';
|
|
|
|
const RequestList: React.FC = () => {
|
|
const router = useRouter();
|
|
const intl = useIntl();
|
|
const { user } = useUser({
|
|
id: Number(router.query.userId),
|
|
});
|
|
const [currentFilter, setCurrentFilter] = useState<Filter>(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 updateQueryParams = useUpdateQueryParams({ page: page.toString() });
|
|
|
|
const {
|
|
data,
|
|
error,
|
|
mutate: revalidate,
|
|
} = useSWR<RequestResultsResponse>(
|
|
`/api/v1/request?take=${currentPageSize}&skip=${
|
|
pageIndex * currentPageSize
|
|
}&filter=${currentFilter}&sort=${currentSort}${
|
|
router.query.userId ? `&requestedBy=${router.query.userId}` : ''
|
|
}`
|
|
);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// If filter value is provided in query, use that instead
|
|
if (Object.values(Filter).includes(router.query.filter as Filter)) {
|
|
setCurrentFilter(router.query.filter as Filter);
|
|
}
|
|
}, [router.query.filter]);
|
|
|
|
// Set filter 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),
|
|
router.query.userId ? user?.displayName : '',
|
|
]}
|
|
/>
|
|
<div className="mb-4 flex flex-col justify-between lg:flex-row lg:items-end">
|
|
<Header
|
|
subtext={
|
|
router.query.userId ? (
|
|
<Link href={`/users/${user?.id}`}>
|
|
<a className="hover:underline">{user?.displayName}</a>
|
|
</Link>
|
|
) : (
|
|
''
|
|
)
|
|
}
|
|
>
|
|
{intl.formatMessage(messages.requests)}
|
|
</Header>
|
|
<div className="mt-2 flex flex-grow flex-col sm:flex-row lg:flex-grow-0">
|
|
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 lg:flex-grow-0">
|
|
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
|
|
<FilterIcon className="h-6 w-6" />
|
|
</span>
|
|
<select
|
|
id="filter"
|
|
name="filter"
|
|
onChange={(e) => {
|
|
setCurrentFilter(e.target.value as Filter);
|
|
router.push({
|
|
pathname: router.pathname,
|
|
query: router.query.userId
|
|
? { userId: router.query.userId }
|
|
: {},
|
|
});
|
|
}}
|
|
value={currentFilter}
|
|
className="rounded-r-only"
|
|
>
|
|
<option value="all">
|
|
{intl.formatMessage(globalMessages.all)}
|
|
</option>
|
|
<option value="pending">
|
|
{intl.formatMessage(globalMessages.pending)}
|
|
</option>
|
|
<option value="approved">
|
|
{intl.formatMessage(globalMessages.approved)}
|
|
</option>
|
|
<option value="processing">
|
|
{intl.formatMessage(globalMessages.processing)}
|
|
</option>
|
|
<option value="available">
|
|
{intl.formatMessage(globalMessages.available)}
|
|
</option>
|
|
<option value="unavailable">
|
|
{intl.formatMessage(globalMessages.unavailable)}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-2 flex flex-grow sm:mb-0 lg:flex-grow-0">
|
|
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
|
|
<SortDescendingIcon className="h-6 w-6" />
|
|
</span>
|
|
<select
|
|
id="sort"
|
|
name="sort"
|
|
onChange={(e) => {
|
|
setCurrentSort(e.target.value as Sort);
|
|
router.push({
|
|
pathname: router.pathname,
|
|
query: router.query.userId
|
|
? { userId: router.query.userId }
|
|
: {},
|
|
});
|
|
}}
|
|
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 w-full flex-col items-center justify-center py-24 text-white">
|
|
<span className="text-2xl text-gray-400">
|
|
{intl.formatMessage(globalMessages.noresults)}
|
|
</span>
|
|
{currentFilter !== Filter.ALL && (
|
|
<div className="mt-4">
|
|
<Button
|
|
buttonType="primary"
|
|
onClick={() => setCurrentFilter(Filter.ALL)}
|
|
>
|
|
{intl.formatMessage(messages.showallrequests)}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="actions">
|
|
<nav
|
|
className="mb-3 flex flex-col items-center space-y-3 sm:flex-row sm:space-y-0"
|
|
aria-label="Pagination"
|
|
>
|
|
<div className="hidden lg:flex lg:flex-1">
|
|
<p className="text-sm">
|
|
{data.results.length > 0 &&
|
|
intl.formatMessage(globalMessages.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="-mt-3 items-center truncate text-sm sm:mt-0">
|
|
{intl.formatMessage(globalMessages.resultsperpage, {
|
|
pageSize: (
|
|
<select
|
|
id="pageSize"
|
|
name="pageSize"
|
|
onChange={(e) => {
|
|
setCurrentPageSize(Number(e.target.value));
|
|
router
|
|
.push({
|
|
pathname: router.pathname,
|
|
query: router.query.userId
|
|
? { userId: router.query.userId }
|
|
: {},
|
|
})
|
|
.then(() => window.scrollTo(0, 0));
|
|
}}
|
|
value={currentPageSize}
|
|
className="short inline"
|
|
>
|
|
<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 flex-auto justify-center space-x-2 sm:flex-1 sm:justify-end">
|
|
<Button
|
|
disabled={!hasPrevPage}
|
|
onClick={() => updateQueryParams('page', (page - 1).toString())}
|
|
>
|
|
<ChevronLeftIcon />
|
|
<span>{intl.formatMessage(globalMessages.previous)}</span>
|
|
</Button>
|
|
<Button
|
|
disabled={!hasNextPage}
|
|
onClick={() => updateQueryParams('page', (page + 1).toString())}
|
|
>
|
|
<span>{intl.formatMessage(globalMessages.next)}</span>
|
|
<ChevronRightIcon />
|
|
</Button>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default RequestList;
|