mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(frontend): a few more tooltips (#2972)
* feat(frontend): a few more tooltips * feat: add tooltips to status badges
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
interface BadgeProps {
|
||||
badgeType?:
|
||||
@@ -14,12 +15,10 @@ interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Badge = ({
|
||||
badgeType = 'default',
|
||||
className,
|
||||
href,
|
||||
children,
|
||||
}: BadgeProps) => {
|
||||
const Badge = (
|
||||
{ badgeType = 'default', className, href, children }: BadgeProps,
|
||||
ref?: React.Ref<HTMLElement>
|
||||
) => {
|
||||
const badgeStyle = [
|
||||
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap',
|
||||
];
|
||||
@@ -79,6 +78,7 @@ const Badge = ({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={badgeStyle.join(' ')}
|
||||
ref={ref as React.Ref<HTMLAnchorElement>}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
@@ -86,12 +86,24 @@ const Badge = ({
|
||||
} else if (href) {
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a className={badgeStyle.join(' ')}>{children}</a>
|
||||
<a
|
||||
className={badgeStyle.join(' ')}
|
||||
ref={ref as React.Ref<HTMLAnchorElement>}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return <span className={badgeStyle.join(' ')}>{children}</span>;
|
||||
return (
|
||||
<span
|
||||
className={badgeStyle.join(' ')}
|
||||
ref={ref as React.Ref<HTMLSpanElement>}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Badge;
|
||||
export default React.forwardRef(Badge) as typeof Badge;
|
||||
|
@@ -70,7 +70,7 @@ const ExternalLinkBlock = ({
|
||||
)}
|
||||
{rtUrl && (
|
||||
<a
|
||||
href={`${rtUrl}`}
|
||||
href={rtUrl}
|
||||
className="w-14 opacity-50 transition duration-300 hover:opacity-100"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@@ -80,6 +80,9 @@ const messages = defineMessages({
|
||||
physicalrelease: 'Physical Release',
|
||||
reportissue: 'Report an Issue',
|
||||
managemovie: 'Manage Movie',
|
||||
rtcriticsscore: 'Rotten Tomatoes Tomatometer',
|
||||
rtaudiencescore: 'Rotten Tomatoes Audience Score',
|
||||
tmdbuserscore: 'TMDB User Score',
|
||||
});
|
||||
|
||||
interface MovieDetailsProps {
|
||||
@@ -322,6 +325,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
tmdbId={data.mediaInfo?.tmdbId}
|
||||
mediaType="movie"
|
||||
plexUrl={data.mediaInfo?.plexUrl}
|
||||
serviceUrl={data.mediaInfo?.serviceUrl}
|
||||
/>
|
||||
{settings.currentSettings.movie4kEnabled &&
|
||||
hasPermission(
|
||||
@@ -343,6 +347,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
tmdbId={data.mediaInfo?.tmdbId}
|
||||
mediaType="movie"
|
||||
plexUrl={data.mediaInfo?.plexUrl4k}
|
||||
serviceUrl={data.mediaInfo?.serviceUrl4k}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -499,36 +504,55 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
(ratingData?.audienceRating && !!ratingData?.audienceScore)) && (
|
||||
<div className="media-ratings">
|
||||
{ratingData?.criticsRating && !!ratingData?.criticsScore && (
|
||||
<>
|
||||
<span className="media-rating">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.rtcriticsscore)}
|
||||
>
|
||||
<a
|
||||
href={ratingData.url}
|
||||
className="media-rating"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ratingData.criticsRating === 'Rotten' ? (
|
||||
<RTRotten className="mr-1 w-6" />
|
||||
<RTRotten className="w-6" />
|
||||
) : (
|
||||
<RTFresh className="mr-1 w-6" />
|
||||
<RTFresh className="w-6" />
|
||||
)}
|
||||
{ratingData.criticsScore}%
|
||||
</span>
|
||||
</>
|
||||
<span>{ratingData.criticsScore}%</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{ratingData?.audienceRating && !!ratingData?.audienceScore && (
|
||||
<>
|
||||
<span className="media-rating">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.rtaudiencescore)}
|
||||
>
|
||||
<a
|
||||
href={ratingData.url}
|
||||
className="media-rating"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ratingData.audienceRating === 'Spilled' ? (
|
||||
<RTAudRotten className="mr-1 w-6" />
|
||||
<RTAudRotten className="w-6" />
|
||||
) : (
|
||||
<RTAudFresh className="mr-1 w-6" />
|
||||
<RTAudFresh className="w-6" />
|
||||
)}
|
||||
{ratingData.audienceScore}%
|
||||
</span>
|
||||
</>
|
||||
<span>{ratingData.audienceScore}%</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!!data.voteCount && (
|
||||
<>
|
||||
<span className="media-rating">
|
||||
<TmdbLogo className="mr-2 w-6" />
|
||||
{data.voteAverage}/10
|
||||
</span>
|
||||
</>
|
||||
<Tooltip content={intl.formatMessage(messages.tmdbuserscore)}>
|
||||
<a
|
||||
href={`https://www.themoviedb.org/movie/${data.id}?language=${locale}`}
|
||||
className="media-rating"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<TmdbLogo className="mr-1 w-6" />
|
||||
<span>{Math.round(data.voteAverage * 10)}%</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import RequestModal from '@app/components/RequestModal';
|
||||
import useRequestOverride from '@app/hooks/useRequestOverride';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
@@ -27,6 +28,13 @@ const messages = defineMessages({
|
||||
profilechanged: 'Quality Profile',
|
||||
rootfolder: 'Root Folder',
|
||||
languageprofile: 'Language Profile',
|
||||
requestdate: 'Request Date',
|
||||
requestedby: 'Requested By',
|
||||
lastmodifiedby: 'Last Modified By',
|
||||
approve: 'Approve Request',
|
||||
decline: 'Decline Request',
|
||||
edit: 'Edit Request',
|
||||
delete: 'Delete Request',
|
||||
});
|
||||
|
||||
interface RequestBlockProps {
|
||||
@@ -83,7 +91,9 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mr-6 min-w-0 flex-1 flex-col items-center text-sm leading-5">
|
||||
<div className="white mb-1 flex flex-nowrap">
|
||||
<Tooltip content={intl.formatMessage(messages.requestedby)}>
|
||||
<UserIcon className="mr-1.5 h-5 w-5 min-w-0 flex-shrink-0" />
|
||||
</Tooltip>
|
||||
<span className="w-40 truncate md:w-auto">
|
||||
<Link
|
||||
href={
|
||||
@@ -100,7 +110,9 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
</div>
|
||||
{request.modifiedBy && (
|
||||
<div className="flex flex-nowrap">
|
||||
<Tooltip content={intl.formatMessage(messages.lastmodifiedby)}>
|
||||
<EyeIcon className="mr-1.5 h-5 w-5 flex-shrink-0" />
|
||||
</Tooltip>
|
||||
<span className="w-40 truncate md:w-auto">
|
||||
<Link
|
||||
href={
|
||||
@@ -120,6 +132,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
<div className="ml-2 flex flex-shrink-0 flex-wrap">
|
||||
{request.status === MediaRequestStatus.PENDING && (
|
||||
<>
|
||||
<Tooltip content={intl.formatMessage(messages.approve)}>
|
||||
<Button
|
||||
buttonType="success"
|
||||
className="mr-1"
|
||||
@@ -128,6 +141,8 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
>
|
||||
<CheckIcon className="icon-sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content={intl.formatMessage(messages.decline)}>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
className="mr-1"
|
||||
@@ -136,6 +151,8 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip content={intl.formatMessage(messages.edit)}>
|
||||
<Button
|
||||
buttonType="primary"
|
||||
onClick={() => setShowEditModal(true)}
|
||||
@@ -143,9 +160,11 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
>
|
||||
<PencilIcon className="icon-sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{request.status !== MediaRequestStatus.PENDING && (
|
||||
<Tooltip content={intl.formatMessage(messages.delete)}>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
onClick={() => deleteRequest()}
|
||||
@@ -153,6 +172,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
>
|
||||
<TrashIcon className="icon-sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -187,7 +207,9 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center text-sm leading-5 sm:mt-0">
|
||||
<Tooltip content={intl.formatMessage(messages.requestdate)}>
|
||||
<CalendarIcon className="mr-1.5 h-5 w-5 flex-shrink-0" />
|
||||
</Tooltip>
|
||||
<span>
|
||||
{intl.formatDate(request.createdAt, {
|
||||
year: 'numeric',
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import RequestModal from '@app/components/RequestModal';
|
||||
import StatusBadge from '@app/components/StatusBadge';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
@@ -31,6 +32,10 @@ const messages = defineMessages({
|
||||
mediaerror: '{mediaType} Not Found',
|
||||
tmdbid: 'TMDB ID',
|
||||
tvdbid: 'TheTVDB ID',
|
||||
approverequest: 'Approve Request',
|
||||
declinerequest: 'Decline Request',
|
||||
editrequest: 'Edit Request',
|
||||
cancelrequest: 'Cancel Request',
|
||||
deleterequest: 'Delete Request',
|
||||
});
|
||||
|
||||
@@ -139,11 +144,9 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
: requestData.media.plexUrl
|
||||
}
|
||||
serviceUrl={
|
||||
hasPermission(Permission.ADMIN)
|
||||
? requestData.is4k
|
||||
requestData.is4k
|
||||
? requestData.media.serviceUrl4k
|
||||
: requestData.media.serviceUrl
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -153,17 +156,29 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
<div className="flex flex-1 items-end space-x-2">
|
||||
{hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
requestData?.media.id && (
|
||||
<>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
className="mt-4"
|
||||
className="mt-4 hidden sm:block"
|
||||
onClick={() => deleteRequest()}
|
||||
>
|
||||
<TrashIcon style={{ marginRight: '0' }} />
|
||||
<span className="ml-1.5 hidden sm:block">
|
||||
{intl.formatMessage(messages.deleterequest)}
|
||||
</span>
|
||||
<TrashIcon />
|
||||
<span>{intl.formatMessage(globalMessages.delete)}</span>
|
||||
</Button>
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.deleterequest)}
|
||||
>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
className="mt-4 sm:hidden"
|
||||
onClick={() => deleteRequest()}
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -389,7 +404,14 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
tmdbId={requestData.media.tmdbId}
|
||||
mediaType={requestData.type}
|
||||
plexUrl={
|
||||
requestData.media[requestData.is4k ? 'plexUrl4k' : 'plexUrl']
|
||||
requestData.is4k
|
||||
? requestData.media.plexUrl4k
|
||||
: requestData.media.plexUrl
|
||||
}
|
||||
serviceUrl={
|
||||
requestData.is4k
|
||||
? requestData.media.serviceUrl4k
|
||||
: requestData.media.serviceUrl
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -415,26 +437,52 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
{requestData.status === MediaRequestStatus.PENDING &&
|
||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<>
|
||||
<div>
|
||||
<Button
|
||||
buttonType="success"
|
||||
buttonSize="sm"
|
||||
className="hidden sm:block"
|
||||
onClick={() => modifyRequest('approve')}
|
||||
>
|
||||
<CheckIcon style={{ marginRight: '0' }} />
|
||||
<span className="ml-1.5 hidden sm:block">
|
||||
{intl.formatMessage(globalMessages.approve)}
|
||||
</span>
|
||||
<CheckIcon />
|
||||
<span>{intl.formatMessage(globalMessages.approve)}</span>
|
||||
</Button>
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.approverequest)}
|
||||
>
|
||||
<Button
|
||||
buttonType="success"
|
||||
buttonSize="sm"
|
||||
className="sm:hidden"
|
||||
onClick={() => modifyRequest('approve')}
|
||||
>
|
||||
<CheckIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
className="hidden sm:block"
|
||||
onClick={() => modifyRequest('decline')}
|
||||
>
|
||||
<XIcon style={{ marginRight: '0' }} />
|
||||
<span className="ml-1.5 hidden sm:block">
|
||||
{intl.formatMessage(globalMessages.decline)}
|
||||
</span>
|
||||
<XIcon />
|
||||
<span>{intl.formatMessage(globalMessages.decline)}</span>
|
||||
</Button>
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.declinerequest)}
|
||||
>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
className="sm:hidden"
|
||||
onClick={() => modifyRequest('decline')}
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{requestData.status === MediaRequestStatus.PENDING &&
|
||||
@@ -442,33 +490,54 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
requestData.requestedBy.id === user?.id &&
|
||||
(requestData.type === 'tv' ||
|
||||
hasPermission(Permission.REQUEST_ADVANCED)) && (
|
||||
<div>
|
||||
{!hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<Button
|
||||
buttonType="primary"
|
||||
buttonSize="sm"
|
||||
className="hidden sm:block"
|
||||
onClick={() => setShowEditModal(true)}
|
||||
className={`${
|
||||
hasPermission(Permission.MANAGE_REQUESTS) ? 'sm:hidden' : ''
|
||||
}`}
|
||||
>
|
||||
<PencilIcon style={{ marginRight: '0' }} />
|
||||
<span className="ml-1.5 hidden sm:block">
|
||||
{intl.formatMessage(globalMessages.edit)}
|
||||
</span>
|
||||
<PencilIcon />
|
||||
<span>{intl.formatMessage(globalMessages.edit)}</span>
|
||||
</Button>
|
||||
)}
|
||||
<Tooltip content={intl.formatMessage(messages.editrequest)}>
|
||||
<Button
|
||||
buttonType="primary"
|
||||
buttonSize="sm"
|
||||
className="sm:hidden"
|
||||
onClick={() => setShowEditModal(true)}
|
||||
>
|
||||
<PencilIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{requestData.status === MediaRequestStatus.PENDING &&
|
||||
!hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
requestData.requestedBy.id === user?.id && (
|
||||
<div>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
className="hidden sm:block"
|
||||
onClick={() => deleteRequest()}
|
||||
>
|
||||
<XIcon style={{ marginRight: '0' }} />
|
||||
<span className="ml-1.5 hidden sm:block">
|
||||
{intl.formatMessage(globalMessages.cancel)}
|
||||
</span>
|
||||
<XIcon />
|
||||
<span>{intl.formatMessage(globalMessages.cancel)}</span>
|
||||
</Button>
|
||||
<Tooltip content={intl.formatMessage(messages.cancelrequest)}>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
className="sm:hidden"
|
||||
onClick={() => deleteRequest()}
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -136,11 +136,9 @@ const RequestItemError = ({
|
||||
: requestData.media.plexUrl
|
||||
}
|
||||
serviceUrl={
|
||||
hasPermission(Permission.ADMIN)
|
||||
? requestData.is4k
|
||||
requestData.is4k
|
||||
? requestData.media.serviceUrl4k
|
||||
: requestData.media.serviceUrl
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -472,9 +470,14 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
tmdbId={requestData.media.tmdbId}
|
||||
mediaType={requestData.type}
|
||||
plexUrl={
|
||||
requestData.media[
|
||||
requestData.is4k ? 'plexUrl4k' : 'plexUrl'
|
||||
]
|
||||
requestData.is4k
|
||||
? requestData.media.plexUrl4k
|
||||
: requestData.media.plexUrl
|
||||
}
|
||||
serviceUrl={
|
||||
requestData.is4k
|
||||
? requestData.media.serviceUrl4k
|
||||
: requestData.media.serviceUrl
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
@@ -46,7 +46,7 @@ const messages = defineMessages({
|
||||
'Do NOT enable this setting unless you understand what you are doing!',
|
||||
cacheImages: 'Enable Image Caching',
|
||||
cacheImagesTip:
|
||||
'Optimize and store all images locally (consumes a significant amount of disk space)',
|
||||
'Cache and serve optimized images (requires a significant amount of disk space)',
|
||||
trustProxy: 'Enable Proxy Support',
|
||||
trustProxyTip:
|
||||
'Allow Overseerr to correctly register client IP addresses behind a proxy',
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import Spinner from '@app/assets/spinner.svg';
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
@@ -9,6 +10,9 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
const messages = defineMessages({
|
||||
status: '{status}',
|
||||
status4k: '4K {status}',
|
||||
playonplex: 'Play on Plex',
|
||||
openinarr: 'Open in {arr}',
|
||||
managemedia: 'Manage {mediaType}',
|
||||
});
|
||||
|
||||
interface StatusBadgeProps {
|
||||
@@ -35,6 +39,7 @@ const StatusBadge = ({
|
||||
const settings = useSettings();
|
||||
|
||||
let mediaLink: string | undefined;
|
||||
let mediaLinkDescription: string | undefined;
|
||||
|
||||
if (
|
||||
mediaType &&
|
||||
@@ -63,63 +68,94 @@ const StatusBadge = ({
|
||||
: settings.currentSettings.series4kEnabled))
|
||||
) {
|
||||
mediaLink = plexUrl;
|
||||
mediaLinkDescription = intl.formatMessage(messages.playonplex);
|
||||
} else if (hasPermission(Permission.MANAGE_REQUESTS)) {
|
||||
mediaLink =
|
||||
mediaType && tmdbId ? `/${mediaType}/${tmdbId}?manage=1` : serviceUrl;
|
||||
if (mediaType && tmdbId) {
|
||||
mediaLink = `/${mediaType}/${tmdbId}?manage=1`;
|
||||
mediaLinkDescription = intl.formatMessage(messages.managemedia, {
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie' ? globalMessages.movie : globalMessages.tvshow
|
||||
),
|
||||
});
|
||||
} else if (hasPermission(Permission.ADMIN)) {
|
||||
mediaLink = serviceUrl;
|
||||
mediaLinkDescription = intl.formatMessage(messages.openinarr, {
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case MediaStatus.AVAILABLE:
|
||||
return (
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="success" href={mediaLink}>
|
||||
<div className="flex items-center">
|
||||
<span>
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
{intl.formatMessage(
|
||||
is4k ? messages.status4k : messages.status,
|
||||
{
|
||||
status: intl.formatMessage(globalMessages.available),
|
||||
})}
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
{inProgress && <Spinner className="ml-1 h-3 w-3" />}
|
||||
</div>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
||||
return (
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="success" href={mediaLink}>
|
||||
<div className="flex items-center">
|
||||
<span>
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
status: intl.formatMessage(globalMessages.partiallyavailable),
|
||||
})}
|
||||
{intl.formatMessage(
|
||||
is4k ? messages.status4k : messages.status,
|
||||
{
|
||||
status: intl.formatMessage(
|
||||
globalMessages.partiallyavailable
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
{inProgress && <Spinner className="ml-1 h-3 w-3" />}
|
||||
</div>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
case MediaStatus.PROCESSING:
|
||||
return (
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="primary" href={mediaLink}>
|
||||
<div className="flex items-center">
|
||||
<span>
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
{intl.formatMessage(
|
||||
is4k ? messages.status4k : messages.status,
|
||||
{
|
||||
status: inProgress
|
||||
? intl.formatMessage(globalMessages.processing)
|
||||
: intl.formatMessage(globalMessages.requested),
|
||||
})}
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
{inProgress && <Spinner className="ml-1 h-3 w-3" />}
|
||||
</div>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
case MediaStatus.PENDING:
|
||||
return (
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="warning" href={mediaLink}>
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
status: intl.formatMessage(globalMessages.pending),
|
||||
})}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
default:
|
||||
|
@@ -79,6 +79,9 @@ const messages = defineMessages({
|
||||
episodeCount: '{episodeCount, plural, one {# Episode} other {# Episodes}}',
|
||||
seasonnumber: 'Season {seasonNumber}',
|
||||
status4k: '4K {status}',
|
||||
rtcriticsscore: 'Rotten Tomatoes Tomatometer',
|
||||
rtaudiencescore: 'Rotten Tomatoes Audience Score',
|
||||
tmdbuserscore: 'TMDB User Score',
|
||||
});
|
||||
|
||||
interface TvDetailsProps {
|
||||
@@ -330,6 +333,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
tmdbId={data.mediaInfo?.tmdbId}
|
||||
mediaType="tv"
|
||||
plexUrl={data.mediaInfo?.plexUrl}
|
||||
serviceUrl={data.mediaInfo?.serviceUrl}
|
||||
/>
|
||||
{settings.currentSettings.series4kEnabled &&
|
||||
hasPermission(
|
||||
@@ -351,6 +355,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
tmdbId={data.mediaInfo?.tmdbId}
|
||||
mediaType="tv"
|
||||
plexUrl={data.mediaInfo?.plexUrl4k}
|
||||
serviceUrl={data.mediaInfo?.serviceUrl4k}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -660,30 +665,55 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
(ratingData?.audienceRating && !!ratingData?.audienceScore)) && (
|
||||
<div className="media-ratings">
|
||||
{ratingData?.criticsRating && !!ratingData?.criticsScore && (
|
||||
<span className="media-rating">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.rtcriticsscore)}
|
||||
>
|
||||
<a
|
||||
href={ratingData.url}
|
||||
className="media-rating"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ratingData.criticsRating === 'Rotten' ? (
|
||||
<RTRotten className="mr-1 w-6" />
|
||||
) : (
|
||||
<RTFresh className="mr-1 w-6" />
|
||||
)}
|
||||
{ratingData.criticsScore}%
|
||||
</span>
|
||||
<span>{ratingData.criticsScore}%</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{ratingData?.audienceRating && !!ratingData?.audienceScore && (
|
||||
<span className="media-rating">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.rtaudiencescore)}
|
||||
>
|
||||
<a
|
||||
href={ratingData.url}
|
||||
className="media-rating"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ratingData.audienceRating === 'Spilled' ? (
|
||||
<RTAudRotten className="mr-1 w-6" />
|
||||
) : (
|
||||
<RTAudFresh className="mr-1 w-6" />
|
||||
)}
|
||||
{ratingData.audienceScore}%
|
||||
</span>
|
||||
<span>{ratingData.audienceScore}%</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!!data.voteCount && (
|
||||
<span className="media-rating">
|
||||
<TmdbLogo className="mr-2 w-6" />
|
||||
{data.voteAverage}/10
|
||||
</span>
|
||||
<Tooltip content={intl.formatMessage(messages.tmdbuserscore)}>
|
||||
<a
|
||||
href={`https://www.themoviedb.org/tv/${data.id}?language=${locale}`}
|
||||
className="media-rating"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<TmdbLogo className="mr-1 w-6" />
|
||||
<span>{Math.round(data.voteAverage * 10)}%</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@@ -181,6 +181,8 @@
|
||||
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Release Date} other {Release Dates}}",
|
||||
"components.MovieDetails.reportissue": "Report an Issue",
|
||||
"components.MovieDetails.revenue": "Revenue",
|
||||
"components.MovieDetails.rtaudiencescore": "Rotten Tomatoes Audience Score",
|
||||
"components.MovieDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer",
|
||||
"components.MovieDetails.runtime": "{minutes} minutes",
|
||||
"components.MovieDetails.showless": "Show Less",
|
||||
"components.MovieDetails.showmore": "Show More",
|
||||
@@ -188,6 +190,7 @@
|
||||
"components.MovieDetails.streamingproviders": "Currently Streaming On",
|
||||
"components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studios}}",
|
||||
"components.MovieDetails.theatricalrelease": "Theatrical Release",
|
||||
"components.MovieDetails.tmdbuserscore": "TMDB User Score",
|
||||
"components.MovieDetails.viewfullcrew": "View Full Crew",
|
||||
"components.MovieDetails.watchtrailer": "Watch Trailer",
|
||||
"components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.",
|
||||
@@ -292,8 +295,15 @@
|
||||
"components.QuotaSelector.unlimited": "Unlimited",
|
||||
"components.RegionSelector.regionDefault": "All Regions",
|
||||
"components.RegionSelector.regionServerDefault": "Default ({region})",
|
||||
"components.RequestBlock.approve": "Approve Request",
|
||||
"components.RequestBlock.decline": "Decline Request",
|
||||
"components.RequestBlock.delete": "Delete Request",
|
||||
"components.RequestBlock.edit": "Edit Request",
|
||||
"components.RequestBlock.languageprofile": "Language Profile",
|
||||
"components.RequestBlock.lastmodifiedby": "Last Modified By",
|
||||
"components.RequestBlock.profilechanged": "Quality Profile",
|
||||
"components.RequestBlock.requestdate": "Request Date",
|
||||
"components.RequestBlock.requestedby": "Requested By",
|
||||
"components.RequestBlock.requestoverrides": "Request Overrides",
|
||||
"components.RequestBlock.rootfolder": "Root Folder",
|
||||
"components.RequestBlock.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
|
||||
@@ -310,7 +320,11 @@
|
||||
"components.RequestButton.requestmore4k": "Request More in 4K",
|
||||
"components.RequestButton.viewrequest": "View Request",
|
||||
"components.RequestButton.viewrequest4k": "View 4K Request",
|
||||
"components.RequestCard.approverequest": "Approve Request",
|
||||
"components.RequestCard.cancelrequest": "Cancel Request",
|
||||
"components.RequestCard.declinerequest": "Decline Request",
|
||||
"components.RequestCard.deleterequest": "Delete Request",
|
||||
"components.RequestCard.editrequest": "Edit Request",
|
||||
"components.RequestCard.failedretry": "Something went wrong while retrying the request.",
|
||||
"components.RequestCard.mediaerror": "{mediaType} Not Found",
|
||||
"components.RequestCard.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
|
||||
@@ -736,7 +750,7 @@
|
||||
"components.Settings.applicationTitle": "Application Title",
|
||||
"components.Settings.applicationurl": "Application URL",
|
||||
"components.Settings.cacheImages": "Enable Image Caching",
|
||||
"components.Settings.cacheImagesTip": "Optimize and store all images locally (consumes a significant amount of disk space)",
|
||||
"components.Settings.cacheImagesTip": "Cache and serve optimized images (requires a significant amount of disk space)",
|
||||
"components.Settings.cancelscan": "Cancel Scan",
|
||||
"components.Settings.copied": "Copied API key to clipboard.",
|
||||
"components.Settings.csrfProtection": "Enable CSRF Protection",
|
||||
@@ -849,6 +863,9 @@
|
||||
"components.Setup.signinMessage": "Get started by signing in with your Plex account",
|
||||
"components.Setup.tip": "Tip",
|
||||
"components.Setup.welcome": "Welcome to Overseerr",
|
||||
"components.StatusBadge.managemedia": "Manage {mediaType}",
|
||||
"components.StatusBadge.openinarr": "Open in {arr}",
|
||||
"components.StatusBadge.playonplex": "Play on Plex",
|
||||
"components.StatusBadge.status": "{status}",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.StatusChecker.appUpdated": "{applicationTitle} Updated",
|
||||
@@ -881,6 +898,8 @@
|
||||
"components.TvDetails.productioncountries": "Production {countryCount, plural, one {Country} other {Countries}}",
|
||||
"components.TvDetails.recommendations": "Recommendations",
|
||||
"components.TvDetails.reportissue": "Report an Issue",
|
||||
"components.TvDetails.rtaudiencescore": "Rotten Tomatoes Audience Score",
|
||||
"components.TvDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer",
|
||||
"components.TvDetails.seasonnumber": "Season {seasonNumber}",
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# Season} other {# Seasons}}",
|
||||
"components.TvDetails.seasonstitle": "Seasons",
|
||||
@@ -888,6 +907,7 @@
|
||||
"components.TvDetails.similar": "Similar Series",
|
||||
"components.TvDetails.status4k": "4K {status}",
|
||||
"components.TvDetails.streamingproviders": "Currently Streaming On",
|
||||
"components.TvDetails.tmdbuserscore": "TMDB User Score",
|
||||
"components.TvDetails.viewfullcrew": "View Full Crew",
|
||||
"components.TvDetails.watchtrailer": "Watch Trailer",
|
||||
"components.UserList.accounttype": "Type",
|
||||
|
@@ -184,11 +184,11 @@
|
||||
}
|
||||
|
||||
.media-ratings {
|
||||
@apply flex items-center justify-center border-b border-gray-700 px-4 py-2 font-medium last:border-b-0;
|
||||
@apply flex items-center justify-center space-x-5 border-b border-gray-700 px-4 py-2 font-medium last:border-b-0;
|
||||
}
|
||||
|
||||
.media-rating {
|
||||
@apply mr-4 flex items-center last:mr-0;
|
||||
@apply flex items-center space-x-1;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
|
Reference in New Issue
Block a user