feat: filter by media type on PersonDetails (#1566)
* feat: add person media type filtering * feat: adjust filter to apply to cast and crew * fix: fix layout issue re #1513 * feat: revert unused server side api changes * feat: move media type filtering to client side Updates person details page to filter cast and crew sections by selected media type re #1513
This commit is contained in:
@@ -7,6 +7,7 @@ import TitleCard from '@app/components/TitleCard';
|
|||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import Error from '@app/pages/_error';
|
import Error from '@app/pages/_error';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { CircleStackIcon } from '@heroicons/react/24/solid';
|
||||||
import type { PersonCombinedCreditsResponse } from '@server/interfaces/api/personInterfaces';
|
import type { PersonCombinedCreditsResponse } from '@server/interfaces/api/personInterfaces';
|
||||||
import type { PersonDetails as PersonDetailsType } from '@server/models/Person';
|
import type { PersonDetails as PersonDetailsType } from '@server/models/Person';
|
||||||
import { groupBy } from 'lodash';
|
import { groupBy } from 'lodash';
|
||||||
@@ -25,9 +26,12 @@ const messages = defineMessages('components.PersonDetails', {
|
|||||||
ascharacter: 'as {character}',
|
ascharacter: 'as {character}',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type MediaType = 'all' | 'movie' | 'tv';
|
||||||
|
|
||||||
const PersonDetails = () => {
|
const PersonDetails = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [currentMediaType, setCurrentMediaType] = useState<string>('all');
|
||||||
const { data, error } = useSWR<PersonDetailsType>(
|
const { data, error } = useSWR<PersonDetailsType>(
|
||||||
`/api/v1/person/${router.query.personId}`
|
`/api/v1/person/${router.query.personId}`
|
||||||
);
|
);
|
||||||
@@ -39,7 +43,11 @@ const PersonDetails = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const sortedCast = useMemo(() => {
|
const sortedCast = useMemo(() => {
|
||||||
const grouped = groupBy(combinedCredits?.cast ?? [], 'id');
|
const filtered = (combinedCredits?.cast ?? []).filter(
|
||||||
|
(media) =>
|
||||||
|
currentMediaType === 'all' || media.mediaType === currentMediaType
|
||||||
|
);
|
||||||
|
const grouped = groupBy(filtered, 'id');
|
||||||
|
|
||||||
const reduced = Object.values(grouped).map((objs) => ({
|
const reduced = Object.values(grouped).map((objs) => ({
|
||||||
...objs[0],
|
...objs[0],
|
||||||
@@ -54,10 +62,14 @@ const PersonDetails = () => {
|
|||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
}, [combinedCredits]);
|
}, [combinedCredits, currentMediaType]);
|
||||||
|
|
||||||
const sortedCrew = useMemo(() => {
|
const sortedCrew = useMemo(() => {
|
||||||
const grouped = groupBy(combinedCredits?.crew ?? [], 'id');
|
const filtered = (combinedCredits?.crew ?? []).filter(
|
||||||
|
(media) =>
|
||||||
|
currentMediaType === 'all' || media.mediaType === currentMediaType
|
||||||
|
);
|
||||||
|
const grouped = groupBy(filtered, 'id');
|
||||||
|
|
||||||
const reduced = Object.values(grouped).map((objs) => ({
|
const reduced = Object.values(grouped).map((objs) => ({
|
||||||
...objs[0],
|
...objs[0],
|
||||||
@@ -72,7 +84,7 @@ const PersonDetails = () => {
|
|||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
}, [combinedCredits]);
|
}, [combinedCredits, currentMediaType]);
|
||||||
|
|
||||||
if (!data && !error) {
|
if (!data && !error) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />;
|
||||||
@@ -122,6 +134,29 @@ const PersonDetails = () => {
|
|||||||
|
|
||||||
const isLoading = !combinedCredits && !errorCombinedCredits;
|
const isLoading = !combinedCredits && !errorCombinedCredits;
|
||||||
|
|
||||||
|
const mediaTypePicker = (
|
||||||
|
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 lg:flex-grow-0">
|
||||||
|
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
|
||||||
|
<CircleStackIcon className="h-6 w-6" />
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
id="mediaType"
|
||||||
|
name="mediaType"
|
||||||
|
onChange={(e) => {
|
||||||
|
setCurrentMediaType(e.target.value as MediaType);
|
||||||
|
}}
|
||||||
|
value={currentMediaType}
|
||||||
|
className="rounded-r-only"
|
||||||
|
>
|
||||||
|
<option value="all">{intl.formatMessage(globalMessages.all)}</option>
|
||||||
|
<option value="movie">
|
||||||
|
{intl.formatMessage(globalMessages.movies)}
|
||||||
|
</option>
|
||||||
|
<option value="tv">{intl.formatMessage(globalMessages.tvshows)}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const cast = (sortedCast ?? []).length > 0 && (
|
const cast = (sortedCast ?? []).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="slider-header">
|
<div className="slider-header">
|
||||||
@@ -235,8 +270,11 @@ const PersonDetails = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-center text-gray-300 lg:text-left">
|
<div className="w-full text-center text-gray-300 lg:text-left">
|
||||||
<h1 className="text-3xl text-white lg:text-4xl">{data.name}</h1>
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<h1 className="text-3xl text-white lg:text-4xl">{data.name}</h1>
|
||||||
|
<div className="flex-shrink-0">{mediaTypePicker}</div>
|
||||||
|
</div>
|
||||||
<div className="mt-1 mb-2 space-y-1 text-xs text-white sm:text-sm lg:text-base">
|
<div className="mt-1 mb-2 space-y-1 text-xs text-white sm:text-sm lg:text-base">
|
||||||
<div>{personAttributes.join(' | ')}</div>
|
<div>{personAttributes.join(' | ')}</div>
|
||||||
{(data.alsoKnownAs ?? []).length > 0 && (
|
{(data.alsoKnownAs ?? []).length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user