feat(plex): person cards in the grid for the filtered actors (S3)
The actors currently in the filter now render as cards above the poster grid — headshot + name + in-scope title count + a remove x — so you SEE who you're exploring, not just chips in the rail (generalises the old search-only person cards to the applied filter). New GET /plex/people/cards (photo from the plex_people cache, count live against cast_names); PlexBrowse fetches + renders them; i18n en/hu. Verified: John Goodman (11) / Douglas M. Griffin (1) / Bradley Cooper (14) with photos + counts. (Plex cast-explore S3)
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
RotateCcw,
|
||||
Star,
|
||||
Tv2,
|
||||
User,
|
||||
X,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
@@ -136,6 +138,17 @@ export default function PlexBrowse() {
|
||||
|
||||
const items = browseQ.data?.pages.flatMap((p) => p.items) ?? [];
|
||||
const total = browseQ.data?.pages[0]?.total ?? 0;
|
||||
// Cards for the actors currently in the filter (name + headshot + in-scope count) — shown above the
|
||||
// grid so you SEE who you're exploring, not just chips in the rail.
|
||||
const peopleCardsQ = useQuery({
|
||||
queryKey: ["plex-people-cards", scope, filters.actors],
|
||||
queryFn: () => api.plexPeopleCards(filters.actors, scope),
|
||||
enabled: sub.view.kind === "grid" && filters.actors.length > 0,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
const personCards = peopleCardsQ.data?.people ?? [];
|
||||
const removeActor = (name: string) =>
|
||||
setFilters({ ...filters, actors: filters.actors.filter((x) => x !== name) });
|
||||
// Episode matches (grouped "Episodes" section) — search-only; same set on every page, take page 0.
|
||||
const episodes = browseQ.data?.pages[0]?.episodes ?? [];
|
||||
|
||||
@@ -280,6 +293,42 @@ export default function PlexBrowse() {
|
||||
</div>
|
||||
</PageToolbar>
|
||||
|
||||
{personCards.length > 0 && (
|
||||
<div className="mb-4 flex flex-wrap gap-3">
|
||||
{personCards.map((p) => (
|
||||
<div
|
||||
key={p.name}
|
||||
className="glass-card flex items-center gap-2.5 rounded-xl py-1.5 pl-1.5 pr-2.5"
|
||||
>
|
||||
{p.photo ? (
|
||||
<img
|
||||
src={p.photo}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
className="w-10 h-10 rounded-full object-cover shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full grid place-items-center bg-surface text-muted shrink-0">
|
||||
<User className="w-5 h-5" />
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium truncate max-w-[150px]">{p.name}</div>
|
||||
<div className="text-[11px] text-muted">{t("plex.people.count", { count: p.count })}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => removeActor(p.name)}
|
||||
title={t("plex.people.remove")}
|
||||
aria-label={t("plex.people.remove")}
|
||||
className="ml-1 p-1 rounded-full text-muted hover:text-fg hover:bg-card transition"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{browseQ.isLoading ? (
|
||||
<p className="text-muted text-sm">{t("plex.loading")}</p>
|
||||
) : items.length === 0 && episodes.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user