import React, { useState, useContext, useMemo } from 'react'; import { FormattedDate, defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import { useRouter } from 'next/router'; import Button from '../Common/Button'; import Link from 'next/link'; import Slider from '../Slider'; import PersonCard from '../PersonCard'; import { LanguageContext } from '../../context/LanguageContext'; import LoadingSpinner from '../Common/LoadingSpinner'; import { useUser, Permission } from '../../hooks/useUser'; import { TvDetails as TvDetailsType } from '../../../server/models/Tv'; import { MediaStatus } from '../../../server/constants/media'; import RequestModal from '../RequestModal'; import axios from 'axios'; import SlideOver from '../Common/SlideOver'; import RequestBlock from '../RequestBlock'; import Error from '../../pages/_error'; import TmdbLogo from '../../assets/tmdb_logo.svg'; import RTFresh from '../../assets/rt_fresh.svg'; import RTRotten from '../../assets/rt_rotten.svg'; import RTAudFresh from '../../assets/rt_aud_fresh.svg'; import RTAudRotten from '../../assets/rt_aud_rotten.svg'; import type { RTRating } from '../../../server/api/rottentomatoes'; import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb/constants'; import ExternalLinkBlock from '../ExternalLinkBlock'; import { sortCrewPriority } from '../../utils/creditHelpers'; import { Crew } from '../../../server/models/common'; import StatusBadge from '../StatusBadge'; import RequestButton from '../RequestButton'; import MediaSlider from '../MediaSlider'; import ConfirmButton from '../Common/ConfirmButton'; import DownloadBlock from '../DownloadBlock'; import PageTitle from '../Common/PageTitle'; import useSettings from '../../hooks/useSettings'; import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; const messages = defineMessages({ firstAirDate: 'First Air Date', nextAirDate: 'Next Air Date', userrating: 'User Rating', status: 'Status', originallanguage: 'Original Language', overview: 'Overview', cast: 'Cast', recommendations: 'Recommendations', similar: 'Similar Series', cancelrequest: 'Cancel Request', watchtrailer: 'Watch Trailer', available: 'Available', unavailable: 'Unavailable', pending: 'Pending', overviewunavailable: 'Overview unavailable.', manageModalTitle: 'Manage Series', manageModalRequests: 'Requests', manageModalNoRequests: 'No Requests', manageModalClearMedia: 'Clear All Media Data', manageModalClearMediaWarning: 'This will irreversibly remove all data for this TV series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next sync.', approve: 'Approve', decline: 'Decline', showtype: 'Show Type', anime: 'Anime', network: 'Network', viewfullcrew: 'View Full Crew', areyousure: 'Are you sure?', opensonarr: 'Open Series in Sonarr', opensonarr4k: 'Open Series in 4K Sonarr', downloadstatus: 'Download Status', playonplex: 'Play on Plex', play4konplex: 'Play 4K on Plex', markavailable: 'Mark as Available', mark4kavailable: 'Mark 4K as Available', allseasonsmarkedavailable: '* All seasons will be marked as available.', }); interface TvDetailsProps { tv?: TvDetailsType; } const TvDetails: React.FC = ({ tv }) => { const settings = useSettings(); const { user, hasPermission } = useUser(); const router = useRouter(); const intl = useIntl(); const { locale } = useContext(LanguageContext); const [showRequestModal, setShowRequestModal] = useState(false); const [showManager, setShowManager] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/tv/${router.query.tvId}?language=${locale}`, { initialData: tv, } ); const { data: ratingData } = useSWR( `/api/v1/tv/${router.query.tvId}/ratings` ); const sortedCrew = useMemo(() => sortCrewPriority(data?.credits.crew ?? []), [ data, ]); if (!data && !error) { return ; } if (!data) { return ; } const mediaLinks: PlayButtonLink[] = []; if (data.mediaInfo?.plexUrl) { mediaLinks.push({ text: intl.formatMessage(messages.playonplex), url: data.mediaInfo?.plexUrl, }); } if ( data.mediaInfo?.plexUrl4k && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { type: 'or', }) ) { mediaLinks.push({ text: intl.formatMessage(messages.play4konplex), url: data.mediaInfo?.plexUrl4k, }); } const trailerUrl = data.relatedVideos ?.filter((r) => r.type === 'Trailer') .sort((a, b) => a.size - b.size) .pop()?.url; if (trailerUrl) { mediaLinks.push({ text: intl.formatMessage(messages.watchtrailer), url: trailerUrl, }); } const deleteMedia = async () => { if (data?.mediaInfo?.id) { await axios.delete(`/api/v1/media/${data?.mediaInfo?.id}`); revalidate(); } }; const markAvailable = async (is4k = false) => { await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, { is4k, }); revalidate(); }; const region = user?.settings?.region ? user.settings.region : settings.currentSettings.region ? settings.currentSettings.region : 'US'; const seriesAttributes: React.ReactNode[] = []; if ( data.contentRatings.results.length && data.contentRatings.results.find( (r) => r.iso_3166_1 === region || data.contentRatings.results[0].rating ) ) { seriesAttributes.push( {data.contentRatings.results.find((r) => r.iso_3166_1 === region) ?.rating || data.contentRatings.results[0].rating} ); } if (data.genres.length) { seriesAttributes.push(data.genres.map((g) => g.name).join(', ')); } const isComplete = data.seasons.filter((season) => season.seasonNumber !== 0).length <= ( data.mediaInfo?.seasons.filter( (season) => season.status === MediaStatus.AVAILABLE ) ?? [] ).length; const is4kComplete = data.seasons.filter((season) => season.seasonNumber !== 0).length <= ( data.mediaInfo?.seasons.filter( (season) => season.status4k === MediaStatus.AVAILABLE ) ?? [] ).length; return (
{ revalidate(); setShowRequestModal(false); }} onCancel={() => setShowRequestModal(false)} /> setShowManager(false)} subText={data.name} > {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( <>

{intl.formatMessage(messages.downloadstatus)}

    {data.mediaInfo?.downloadStatus?.map((status, index) => (
  • ))} {data.mediaInfo?.downloadStatus4k?.map((status, index) => (
  • ))}
)} {data?.mediaInfo && (data.mediaInfo.status !== MediaStatus.AVAILABLE || (data.mediaInfo.status4k !== MediaStatus.AVAILABLE && settings.currentSettings.series4kEnabled)) && (
{data?.mediaInfo && data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
)} {data?.mediaInfo && data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && settings.currentSettings.series4kEnabled && (
)}
{intl.formatMessage(messages.allseasonsmarkedavailable)}
)}

{intl.formatMessage(messages.manageModalRequests)}

    {data.mediaInfo?.requests?.map((request) => (
  • revalidate()} />
  • ))} {(data.mediaInfo?.requests ?? []).length === 0 && (
  • {intl.formatMessage(messages.manageModalNoRequests)}
  • )}
{(data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && (
{data?.mediaInfo?.serviceUrl && ( )} {data?.mediaInfo?.serviceUrl4k && ( )}
)} {data?.mediaInfo && (
deleteMedia()} confirmText={intl.formatMessage(messages.areyousure)} className="w-full" > {intl.formatMessage(messages.manageModalClearMedia)}
{intl.formatMessage(messages.manageModalClearMediaWarning)}
)}
0} plexUrl={data.mediaInfo?.plexUrl} /> {settings.currentSettings.series4kEnabled && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { type: 'or', }) && ( 0 } plexUrl4k={data.mediaInfo?.plexUrl4k} /> )}

{data.name}{' '} {data.firstAirDate && ( ({data.firstAirDate.slice(0, 4)}) )}

{seriesAttributes.length > 0 && seriesAttributes .map((t, k) => {t}) .reduce((prev, curr) => ( <> {prev} | {curr} ))}
revalidate()} tmdbId={data?.id} media={data?.mediaInfo} isShowComplete={isComplete} is4kShowComplete={is4kComplete} />
{hasPermission(Permission.MANAGE_REQUESTS) && ( )}

{intl.formatMessage(messages.overview)}

{data.overview ? data.overview : intl.formatMessage(messages.overviewunavailable)}

    {(data.createdBy.length > 0 ? [ ...data.createdBy.map( (person): Partial => ({ id: person.id, job: 'Creator', name: person.name, }) ), ...sortedCrew, ] : sortedCrew ) .slice(0, 6) .map((person) => (
  • {person.job} {person.name}
  • ))}
{sortedCrew.length > 0 && ( )}
{(!!data.voteCount || (ratingData?.criticsRating && !!ratingData?.criticsScore) || (ratingData?.audienceRating && !!ratingData?.audienceScore)) && (
{ratingData?.criticsRating && !!ratingData?.criticsScore && ( <> {ratingData.criticsRating === 'Rotten' ? ( ) : ( )} {ratingData.criticsScore}% )} {ratingData?.audienceRating && !!ratingData?.audienceScore && ( <> {ratingData.audienceRating === 'Spilled' ? ( ) : ( )} {ratingData.audienceScore}% )} {!!data.voteCount && ( <> {data.voteAverage}/10 )}
)} {data.keywords.some( (keyword) => keyword.id === ANIME_KEYWORD_ID ) && (
{intl.formatMessage(messages.showtype)} {intl.formatMessage(messages.anime)}
)} {data.firstAirDate && (
{intl.formatMessage(messages.firstAirDate)}
)} {data.nextEpisodeToAir && (
{intl.formatMessage(messages.nextAirDate)}
)}
{intl.formatMessage(messages.status)} {data.status}
{data.spokenLanguages.some( (lng) => lng.iso_639_1 === data.originalLanguage ) && (
{intl.formatMessage(messages.originallanguage)} { data.spokenLanguages.find( (lng) => lng.iso_639_1 === data.originalLanguage )?.name }
)} {data.networks.length > 0 && (
{intl.formatMessage(messages.network)} {data.networks.map((n) => n.name).join(', ')}
)}
{data.credits.cast.length > 0 && ( <> ( ))} /> )}
); }; export default TvDetails;