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 { issueOptions } from '@app/components/IssueModal/constants'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { EyeIcon } from '@heroicons/react/24/solid'; import { IssueStatus } from '@server/constants/issue'; import { MediaType } from '@server/constants/media'; import type Issue from '@server/entity/Issue'; import type { MovieDetails } from '@server/models/Movie'; import type { TvDetails } from '@server/models/Tv'; import Link from 'next/link'; import { useInView } from 'react-intersection-observer'; import { FormattedRelativeTime, useIntl } from 'react-intl'; import useSWR from 'swr'; const messages = defineMessages('components.IssueList.IssueItem', { openeduserdate: '{date} by {user}', seasons: '{seasonCount, plural, one {Season} other {Seasons}}', episodes: '{episodeCount, plural, one {Episode} other {Episodes}}', problemepisode: 'Affected Episode', issuetype: 'Type', issuestatus: 'Status', opened: 'Opened', viewissue: 'View Issue', unknownissuetype: 'Unknown', descriptionpreview: 'Issue Description', }); const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { return (movie as MovieDetails).title !== undefined; }; interface IssueItemProps { issue: Issue; } const IssueItem = ({ issue }: IssueItemProps) => { const intl = useIntl(); const { hasPermission } = useUser(); const { ref, inView } = useInView({ triggerOnce: true, }); const url = issue.media.mediaType === 'movie' ? `/api/v1/movie/${issue.media.tmdbId}` : `/api/v1/tv/${issue.media.tmdbId}`; const { data: title, error } = useSWR( inView ? url : null ); if (!title && !error) { return (
); } if (!title) { return
uh oh
; } const issueOption = issueOptions.find( (opt) => opt.issueType === issue?.issueType ); const problemSeasonEpisodeLine: React.ReactNode[] = []; if (!isMovie(title) && issue) { problemSeasonEpisodeLine.push( <> {intl.formatMessage(messages.seasons, { seasonCount: issue.problemSeason ? 1 : 0, })} {issue.problemSeason > 0 ? issue.problemSeason : intl.formatMessage(globalMessages.all)} ); if (issue.problemSeason > 0) { problemSeasonEpisodeLine.push( <> {intl.formatMessage(messages.episodes, { episodeCount: issue.problemEpisode ? 1 : 0, })} {issue.problemEpisode > 0 ? issue.problemEpisode : intl.formatMessage(globalMessages.all)} ); } } const description = issue.comments?.[0]?.message || ''; const maxDescriptionLength = 120; const shouldTruncate = description.length > maxDescriptionLength; const truncatedDescription = shouldTruncate ? description.substring(0, maxDescriptionLength) + '...' : description; return (
{title.backdropPath && (
)}
{(isMovie(title) ? title.releaseDate : title.firstAirDate)?.slice( 0, 4 )}
{isMovie(title) ? title.title : title.name} {description && (
{shouldTruncate ? (
Issue Description
{description}
} tooltipConfig={{ placement: 'top', offset: [0, 8], }} > {truncatedDescription} ) : ( {description} )}
)} {problemSeasonEpisodeLine.length > 0 && (
{problemSeasonEpisodeLine.map((t, k) => ( {t} ))}
)}
{intl.formatMessage(messages.issuestatus)} {issue.status === IssueStatus.OPEN ? ( {intl.formatMessage(globalMessages.open)} ) : ( {intl.formatMessage(globalMessages.resolved)} )}
{intl.formatMessage(messages.issuetype)} {intl.formatMessage( issueOption?.name ?? messages.unknownissuetype )}
{hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { type: 'or', }) ? ( <> {intl.formatMessage(messages.opened)} {intl.formatMessage(messages.openeduserdate, { date: ( ), user: ( {issue.createdBy.displayName} ), })} ) : ( <> {intl.formatMessage(messages.opened)} )}
); }; export default IssueItem;