mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(frontend): open media management slideover on status badge click (#2407)
* feat(frontend): open media management slideover on status badge click * fix(frontend): use Link component for in-app badge links * fix: check for query param value of '1' * fix: correct query param check * fix: available badges should still link to Plex
This commit is contained in:
@@ -1,22 +1,23 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface BadgeProps {
|
interface BadgeProps {
|
||||||
badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
|
badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
|
||||||
className?: string;
|
className?: string;
|
||||||
url?: string;
|
href?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Badge: React.FC<BadgeProps> = ({
|
const Badge: React.FC<BadgeProps> = ({
|
||||||
badgeType = 'default',
|
badgeType = 'default',
|
||||||
className,
|
className,
|
||||||
url,
|
href,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const badgeStyle = [
|
const badgeStyle = [
|
||||||
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap',
|
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (url) {
|
if (href) {
|
||||||
badgeStyle.push('transition cursor-pointer !no-underline');
|
badgeStyle.push('transition cursor-pointer !no-underline');
|
||||||
} else {
|
} else {
|
||||||
badgeStyle.push('cursor-default');
|
badgeStyle.push('cursor-default');
|
||||||
@@ -25,25 +26,25 @@ const Badge: React.FC<BadgeProps> = ({
|
|||||||
switch (badgeType) {
|
switch (badgeType) {
|
||||||
case 'danger':
|
case 'danger':
|
||||||
badgeStyle.push('bg-red-600 !text-red-100');
|
badgeStyle.push('bg-red-600 !text-red-100');
|
||||||
if (url) {
|
if (href) {
|
||||||
badgeStyle.push('hover:bg-red-500');
|
badgeStyle.push('hover:bg-red-500');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
badgeStyle.push('bg-yellow-500 !text-yellow-100');
|
badgeStyle.push('bg-yellow-500 !text-yellow-100');
|
||||||
if (url) {
|
if (href) {
|
||||||
badgeStyle.push('hover:bg-yellow-400');
|
badgeStyle.push('hover:bg-yellow-400');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'success':
|
case 'success':
|
||||||
badgeStyle.push('bg-green-500 !text-green-100');
|
badgeStyle.push('bg-green-500 !text-green-100');
|
||||||
if (url) {
|
if (href) {
|
||||||
badgeStyle.push('hover:bg-green-400');
|
badgeStyle.push('hover:bg-green-400');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
badgeStyle.push('bg-indigo-500 !text-indigo-100');
|
badgeStyle.push('bg-indigo-500 !text-indigo-100');
|
||||||
if (url) {
|
if (href) {
|
||||||
badgeStyle.push('hover:bg-indigo-400');
|
badgeStyle.push('hover:bg-indigo-400');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,10 +53,10 @@ const Badge: React.FC<BadgeProps> = ({
|
|||||||
badgeStyle.push(className);
|
badgeStyle.push(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
if (href?.includes('://')) {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={url}
|
href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={badgeStyle.join(' ')}
|
className={badgeStyle.join(' ')}
|
||||||
@@ -63,6 +64,12 @@ const Badge: React.FC<BadgeProps> = ({
|
|||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
} else if (href) {
|
||||||
|
return (
|
||||||
|
<Link href={href}>
|
||||||
|
<a className={badgeStyle.join(' ')}>{children}</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <span className={badgeStyle.join(' ')}>{children}</span>;
|
return <span className={badgeStyle.join(' ')}>{children}</span>;
|
||||||
}
|
}
|
||||||
|
@@ -183,11 +183,11 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
|
|||||||
{intl.formatMessage(messages.issuestatus)}
|
{intl.formatMessage(messages.issuestatus)}
|
||||||
</span>
|
</span>
|
||||||
{issue.status === IssueStatus.OPEN ? (
|
{issue.status === IssueStatus.OPEN ? (
|
||||||
<Badge badgeType="warning">
|
<Badge badgeType="warning" href={`/issues/${issue.id}`}>
|
||||||
{intl.formatMessage(globalMessages.open)}
|
{intl.formatMessage(globalMessages.open)}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Badge badgeType="success">
|
<Badge badgeType="success" href={`/issues/${issue.id}`}>
|
||||||
{intl.formatMessage(globalMessages.resolved)}
|
{intl.formatMessage(globalMessages.resolved)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
@@ -85,7 +85,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const [showManager, setShowManager] = useState(false);
|
const [showManager, setShowManager] = useState(
|
||||||
|
router.query.manage == '1' ? true : false
|
||||||
|
);
|
||||||
const minStudios = 3;
|
const minStudios = 3;
|
||||||
const [showMoreStudios, setShowMoreStudios] = useState(false);
|
const [showMoreStudios, setShowMoreStudios] = useState(false);
|
||||||
const [showIssueModal, setShowIssueModal] = useState(false);
|
const [showIssueModal, setShowIssueModal] = useState(false);
|
||||||
@@ -276,12 +278,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
|
tmdbId={data.mediaInfo?.tmdbId}
|
||||||
|
mediaType="movie"
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
plexUrl={data.mediaInfo?.plexUrl}
|
||||||
serviceUrl={
|
|
||||||
hasPermission(Permission.ADMIN)
|
|
||||||
? data.mediaInfo?.serviceUrl
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{settings.currentSettings.movie4kEnabled &&
|
{settings.currentSettings.movie4kEnabled &&
|
||||||
hasPermission(
|
hasPermission(
|
||||||
@@ -296,12 +295,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
inProgress={
|
inProgress={
|
||||||
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
||||||
}
|
}
|
||||||
|
tmdbId={data.mediaInfo?.tmdbId}
|
||||||
|
mediaType="movie"
|
||||||
plexUrl={data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.plexUrl4k}
|
||||||
serviceUrl={
|
|
||||||
hasPermission(Permission.ADMIN)
|
|
||||||
? data.mediaInfo?.serviceUrl4k
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -271,13 +271,17 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
|
|||||||
<span className="hidden mr-2 font-bold sm:block">
|
<span className="hidden mr-2 font-bold sm:block">
|
||||||
{intl.formatMessage(globalMessages.status)}
|
{intl.formatMessage(globalMessages.status)}
|
||||||
</span>
|
</span>
|
||||||
{requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
|
{requestData.status === MediaRequestStatus.DECLINED ? (
|
||||||
MediaStatus.UNKNOWN ||
|
|
||||||
requestData.status === MediaRequestStatus.DECLINED ? (
|
|
||||||
<Badge badgeType="danger">
|
<Badge badgeType="danger">
|
||||||
{requestData.status === MediaRequestStatus.DECLINED
|
{intl.formatMessage(globalMessages.declined)}
|
||||||
? intl.formatMessage(globalMessages.declined)
|
</Badge>
|
||||||
: intl.formatMessage(globalMessages.failed)}
|
) : requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
|
||||||
|
MediaStatus.UNKNOWN ? (
|
||||||
|
<Badge
|
||||||
|
badgeType="danger"
|
||||||
|
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(globalMessages.failed)}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
@@ -292,17 +296,10 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
|
|||||||
).length > 0
|
).length > 0
|
||||||
}
|
}
|
||||||
is4k={requestData.is4k}
|
is4k={requestData.is4k}
|
||||||
|
tmdbId={requestData.media.tmdbId}
|
||||||
|
mediaType={requestData.type}
|
||||||
plexUrl={
|
plexUrl={
|
||||||
requestData.is4k
|
requestData.media[requestData.is4k ? 'plexUrl4k' : 'plexUrl']
|
||||||
? requestData.media.plexUrl4k
|
|
||||||
: requestData.media.plexUrl
|
|
||||||
}
|
|
||||||
serviceUrl={
|
|
||||||
hasPermission(Permission.ADMIN)
|
|
||||||
? requestData.is4k
|
|
||||||
? requestData.media.serviceUrl4k
|
|
||||||
: requestData.media.serviceUrl
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -272,13 +272,18 @@ const RequestItem: React.FC<RequestItemProps> = ({
|
|||||||
<span className="card-field-name">
|
<span className="card-field-name">
|
||||||
{intl.formatMessage(globalMessages.status)}
|
{intl.formatMessage(globalMessages.status)}
|
||||||
</span>
|
</span>
|
||||||
{requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
|
{requestData.status === MediaRequestStatus.DECLINED ? (
|
||||||
MediaStatus.UNKNOWN ||
|
|
||||||
requestData.status === MediaRequestStatus.DECLINED ? (
|
|
||||||
<Badge badgeType="danger">
|
<Badge badgeType="danger">
|
||||||
{requestData.status === MediaRequestStatus.DECLINED
|
{intl.formatMessage(globalMessages.declined)}
|
||||||
? intl.formatMessage(globalMessages.declined)
|
</Badge>
|
||||||
: intl.formatMessage(globalMessages.failed)}
|
) : requestData.media[
|
||||||
|
requestData.is4k ? 'status4k' : 'status'
|
||||||
|
] === MediaStatus.UNKNOWN ? (
|
||||||
|
<Badge
|
||||||
|
badgeType="danger"
|
||||||
|
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(globalMessages.failed)}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
@@ -293,17 +298,12 @@ const RequestItem: React.FC<RequestItemProps> = ({
|
|||||||
).length > 0
|
).length > 0
|
||||||
}
|
}
|
||||||
is4k={requestData.is4k}
|
is4k={requestData.is4k}
|
||||||
|
tmdbId={requestData.media.tmdbId}
|
||||||
|
mediaType={requestData.type}
|
||||||
plexUrl={
|
plexUrl={
|
||||||
requestData.is4k
|
requestData.media[
|
||||||
? requestData.media.plexUrl4k
|
requestData.is4k ? 'plexUrl4k' : 'plexUrl'
|
||||||
: requestData.media.plexUrl
|
]
|
||||||
}
|
|
||||||
serviceUrl={
|
|
||||||
hasPermission(Permission.ADMIN)
|
|
||||||
? requestData.is4k
|
|
||||||
? requestData.media.serviceUrl4k
|
|
||||||
: requestData.media.serviceUrl
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { MediaStatus } from '../../../server/constants/media';
|
import { MediaStatus } from '../../../server/constants/media';
|
||||||
import Spinner from '../../assets/spinner.svg';
|
import Spinner from '../../assets/spinner.svg';
|
||||||
|
import { Permission, useUser } from '../../hooks/useUser';
|
||||||
import globalMessages from '../../i18n/globalMessages';
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
import Badge from '../Common/Badge';
|
import Badge from '../Common/Badge';
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ interface StatusBadgeProps {
|
|||||||
inProgress?: boolean;
|
inProgress?: boolean;
|
||||||
plexUrl?: string;
|
plexUrl?: string;
|
||||||
serviceUrl?: string;
|
serviceUrl?: string;
|
||||||
|
tmdbId?: number;
|
||||||
|
mediaType?: 'movie' | 'tv';
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusBadge: React.FC<StatusBadgeProps> = ({
|
const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||||
@@ -24,13 +27,21 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
|||||||
inProgress = false,
|
inProgress = false,
|
||||||
plexUrl,
|
plexUrl,
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
tmdbId,
|
||||||
|
mediaType,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { hasPermission } = useUser();
|
||||||
|
|
||||||
|
const manageLink =
|
||||||
|
tmdbId && mediaType && hasPermission(Permission.MANAGE_REQUESTS)
|
||||||
|
? `/${mediaType}/${tmdbId}?manage=1`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case MediaStatus.AVAILABLE:
|
case MediaStatus.AVAILABLE:
|
||||||
return (
|
return (
|
||||||
<Badge badgeType="success" url={plexUrl}>
|
<Badge badgeType="success" href={plexUrl}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
@@ -44,7 +55,7 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
|||||||
|
|
||||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
case MediaStatus.PARTIALLY_AVAILABLE:
|
||||||
return (
|
return (
|
||||||
<Badge badgeType="success" url={plexUrl}>
|
<Badge badgeType="success" href={plexUrl}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
@@ -58,7 +69,7 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
|||||||
|
|
||||||
case MediaStatus.PROCESSING:
|
case MediaStatus.PROCESSING:
|
||||||
return (
|
return (
|
||||||
<Badge badgeType="primary" url={serviceUrl}>
|
<Badge badgeType="primary" href={manageLink ?? serviceUrl}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
@@ -74,7 +85,7 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
|||||||
|
|
||||||
case MediaStatus.PENDING:
|
case MediaStatus.PENDING:
|
||||||
return (
|
return (
|
||||||
<Badge badgeType="warning">
|
<Badge badgeType="warning" href={manageLink}>
|
||||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
status: intl.formatMessage(globalMessages.pending),
|
status: intl.formatMessage(globalMessages.pending),
|
||||||
})}
|
})}
|
||||||
|
@@ -80,7 +80,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const [showRequestModal, setShowRequestModal] = useState(false);
|
const [showRequestModal, setShowRequestModal] = useState(false);
|
||||||
const [showManager, setShowManager] = useState(false);
|
const [showManager, setShowManager] = useState(
|
||||||
|
router.query.manage == '1' ? true : false
|
||||||
|
);
|
||||||
const [showIssueModal, setShowIssueModal] = useState(false);
|
const [showIssueModal, setShowIssueModal] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -278,12 +280,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
|
tmdbId={data.mediaInfo?.tmdbId}
|
||||||
|
mediaType="tv"
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
plexUrl={data.mediaInfo?.plexUrl}
|
||||||
serviceUrl={
|
|
||||||
hasPermission(Permission.ADMIN)
|
|
||||||
? data.mediaInfo?.serviceUrl
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{settings.currentSettings.series4kEnabled &&
|
{settings.currentSettings.series4kEnabled &&
|
||||||
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
|
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
|
||||||
@@ -295,12 +294,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
inProgress={
|
inProgress={
|
||||||
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
||||||
}
|
}
|
||||||
|
tmdbId={data.mediaInfo?.tmdbId}
|
||||||
|
mediaType="tv"
|
||||||
plexUrl={data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.plexUrl4k}
|
||||||
serviceUrl={
|
|
||||||
hasPermission(Permission.ADMIN)
|
|
||||||
? data.mediaInfo?.serviceUrl4k
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user