feat(frontend): filter/sorting for request list

closes #431
pull/570/head^2
sct 4 years ago
parent a524b9c4c8
commit 5add44cfb0

@ -100,7 +100,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
if (!title && !error) {
return (
<tr className="w-full bg-gray-800 animate-pulse h-24" ref={ref}>
<tr className="w-full h-24 bg-gray-800 animate-pulse" ref={ref}>
<td colSpan={6}></td>
</tr>
);
@ -108,61 +108,62 @@ const RequestItem: React.FC<RequestItemProps> = ({
if (!title || !requestData) {
return (
<tr className="w-full bg-gray-800 animate-pulse h-24">
<tr className="w-full h-24 bg-gray-800 animate-pulse">
<td colSpan={6}></td>
</tr>
);
}
return (
<tr className="w-full bg-gray-800 h-24 p-2 relative text-white">
<Table.TD
noPadding
className="w-20 px-4 relative hidden sm:table-cell align-middle"
>
<Link
href={
request.type === 'movie'
? `/movie/${request.media.tmdbId}`
: `/tv/${request.media.tmdbId}`
}
>
<a>
<img
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`}
alt=""
className="rounded-md shadow-sm cursor-pointer transition transform-gpu duration-300 scale-100 hover:scale-105 hover:shadow-md"
/>
</a>
</Link>
</Table.TD>
<tr className="relative w-full h-24 p-2 text-white bg-gray-800">
<Table.TD>
<Link
href={
requestData.type === 'movie'
? `/movie/${requestData.media.tmdbId}`
: `/tv/${requestData.media.tmdbId}`
}
>
<a className="text-white text-xl mr-2 hover:underline">
{isMovie(title) ? title.title : title.name}
</a>
</Link>
<div className="text-sm">
{intl.formatMessage(messages.requestedby, {
username: requestData.requestedBy.username,
})}
</div>
{requestData.seasons.length > 0 && (
<div className="hidden mt-2 text-sm sm:flex items-center">
<span className="mr-2">{intl.formatMessage(messages.seasons)}</span>
{requestData.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>{season.seasonNumber}</Badge>
</span>
))}
<div className="flex items-center">
<Link
href={
request.type === 'movie'
? `/movie/${request.media.tmdbId}`
: `/tv/${request.media.tmdbId}`
}
>
<a className="flex-shrink-0 hidden mr-4 sm:block">
<img
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`}
alt=""
className="w-12 transition duration-300 scale-100 rounded-md shadow-sm cursor-pointer transform-gpu hover:scale-105 hover:shadow-md"
/>
</a>
</Link>
<div className="flex-shrink overflow-hidden">
<Link
href={
requestData.type === 'movie'
? `/movie/${requestData.media.tmdbId}`
: `/tv/${requestData.media.tmdbId}`
}
>
<a className="min-w-0 mr-2 text-xl text-white truncate hover:underline">
{isMovie(title) ? title.title : title.name}
</a>
</Link>
<div className="text-sm">
{intl.formatMessage(messages.requestedby, {
username: requestData.requestedBy.username,
})}
</div>
{requestData.seasons.length > 0 && (
<div className="items-center hidden mt-2 text-sm sm:flex">
<span className="mr-2">
{intl.formatMessage(messages.seasons)}
</span>
{requestData.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>{season.seasonNumber}</Badge>
</span>
))}
</div>
)}
</div>
)}
</div>
</Table.TD>
<Table.TD>
{requestData.media.status === MediaStatus.UNKNOWN ? (

@ -18,14 +18,28 @@ const messages = defineMessages({
'Showing <strong>{from}</strong> to <strong>{to}</strong> of <strong>{total}</strong> results',
next: 'Next',
previous: 'Previous',
filterAll: 'All',
filterPending: 'Pending',
filterApproved: 'Approved',
noresults: 'No Results.',
showallrequests: 'Show All Requests',
sortAdded: 'Request Date',
sortModified: 'Last Modified',
});
type Filter = 'all' | 'approved' | 'pending';
type Sort = 'added' | 'modified';
const RequestList: React.FC = () => {
const intl = useIntl();
const [pageIndex, setPageIndex] = useState(0);
const [currentFilter, setCurrentFilter] = useState<Filter>('pending');
const [currentSort, setCurrentSort] = useState<Sort>('added');
const { data, error, revalidate } = useSWR<RequestResultsResponse>(
`/api/v1/request?take=10&skip=${pageIndex * 10}`
`/api/v1/request?take=10&skip=${
pageIndex * 10
}&filter=${currentFilter}&sort=${currentSort}`
);
if (!data && !error) {
return <LoadingSpinner />;
@ -40,10 +54,86 @@ const RequestList: React.FC = () => {
return (
<>
<Header>{intl.formatMessage(messages.requests)}</Header>
<div className="flex flex-col justify-between md:items-end md:flex-row">
<Header>{intl.formatMessage(messages.requests)}</Header>
<div className="flex flex-col md:flex-row">
<div className="flex mb-2 md:mb-0 md:mr-2">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm">
<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) => {
setPageIndex(0);
setCurrentFilter(e.target.value as Filter);
}}
onBlur={(e) => {
setPageIndex(0);
setCurrentFilter(e.target.value as Filter);
}}
value={currentFilter}
className="flex-1 block w-full py-2 pl-3 pr-10 text-base leading-6 text-white bg-gray-700 border-gray-500 rounded-r-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
>
<option value="all">
{intl.formatMessage(messages.filterAll)}
</option>
<option value="pending">
{intl.formatMessage(messages.filterPending)}
</option>
<option value="approved">
{intl.formatMessage(messages.filterApproved)}
</option>
</select>
</div>
<div className="flex">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm">
<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) => {
setPageIndex(0);
setCurrentSort(e.target.value as Sort);
}}
onBlur={(e) => {
setPageIndex(0);
setCurrentSort(e.target.value as Sort);
}}
value={currentSort}
className="flex-1 block w-full py-2 pl-3 pr-10 text-base leading-6 text-white bg-gray-700 border-gray-500 rounded-r-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
>
<option value="added">
{intl.formatMessage(messages.sortAdded)}
</option>
<option value="modified">
{intl.formatMessage(messages.sortModified)}
</option>
</select>
</div>
</div>
</div>
<Table>
<thead>
<Table.TH className="hidden sm:table-cell"></Table.TH>
<Table.TH>{intl.formatMessage(messages.mediaInfo)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.status)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.requestedAt)}</Table.TH>
@ -60,6 +150,27 @@ const RequestList: React.FC = () => {
/>
);
})}
{data.results.length === 0 && (
<tr className="relative w-full h-24 p-2 text-white bg-gray-800">
<Table.TD colSpan={6} noPadding>
<div className="flex flex-col items-center justify-center p-4">
<span>{intl.formatMessage(messages.noresults)}</span>
{currentFilter !== 'all' && (
<div className="mt-4">
<Button
buttonSize="sm"
buttonType="primary"
onClick={() => setCurrentFilter('all')}
>
{intl.formatMessage(messages.showallrequests)}
</Button>
</div>
)}
</div>
</Table.TD>
</tr>
)}
<tr>
<Table.TD colSpan={6} noPadding>
<nav

Loading…
Cancel
Save