fix(jellyseer): resolve request titles via /movie|tv endpoints
The requests table showed all names as "—" because Jellyseerr's /api/v1/request
list does NOT embed titles — they live on the Movie/Series records. Added
JellyseerrClient._resolve_title(media_type, tmdb_id) that fetches
/api/v1/movie/{tmdbId} (→ title) or /api/v1/tv/{tmdbId} (→ name), cached on
the client instance so subsequent polls are instant.
Also scoped the table fetch to open requests only (pending + approved) via
Jellyseerr's filter param, instead of fetching all 800+ historical requests.
open_requests() fetches pending+approved (paginated), resolves their titles
(small set → fast), and returns them sorted by date added desc.
Updated the frontend table's status filter to Open/Pending/Approved (the data
only contains open requests now).
Tests: title resolution end-to-end (movie tmdbId → title), caching across
polls, filter param used. 404/404 backend + 184/184 frontend + build green.
This commit is contained in:
@@ -20,7 +20,13 @@ import { ArrowDown, ArrowUp, ChevronsUpDown, Search } from "lucide-react";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
@@ -35,14 +41,20 @@ import { useJellyseerRequests } from "../hooks/useJellyseer";
|
||||
import type { JellyseerRequest } from "../api/jellyseerr";
|
||||
|
||||
const OPEN_STATUSES = new Set(["pending", "approved", "processing"]);
|
||||
type StatusFilter = "open" | "all" | "pending" | "approved" | "declined";
|
||||
type StatusFilter = "open" | "pending" | "approved";
|
||||
|
||||
function formatDate(v?: number | string): string {
|
||||
if (!v) return "—";
|
||||
const n = Number(v);
|
||||
const ms = n > 1e12 ? n : n * 1000; // seconds -> ms
|
||||
const d = new Date(ms);
|
||||
return Number.isNaN(d.getTime()) ? String(v) : d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
||||
return Number.isNaN(d.getTime())
|
||||
? String(v)
|
||||
: d.toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
const columns: ColumnDef<JellyseerRequest>[] = [
|
||||
@@ -86,7 +98,11 @@ const columns: ColumnDef<JellyseerRequest>[] = [
|
||||
];
|
||||
|
||||
export function JellyseerRequestsTable({ serviceId }: { serviceId: string }) {
|
||||
const { data: requests = [], isLoading, error } = useJellyseerRequests(serviceId);
|
||||
const {
|
||||
data: requests = [],
|
||||
isLoading,
|
||||
error,
|
||||
} = useJellyseerRequests(serviceId);
|
||||
const [sorting, setSorting] = useState<SortingState>([
|
||||
{ id: "created_at", desc: true },
|
||||
]);
|
||||
@@ -99,10 +115,16 @@ export function JellyseerRequestsTable({ serviceId }: { serviceId: string }) {
|
||||
const status = String(r.status ?? "");
|
||||
if (statusFilter === "open") {
|
||||
if (!OPEN_STATUSES.has(status)) return false;
|
||||
} else if (statusFilter !== "all" && status !== statusFilter) {
|
||||
} else if (status !== statusFilter) {
|
||||
return false;
|
||||
}
|
||||
if (q && !String(r.name ?? "").toLowerCase().includes(q)) return false;
|
||||
if (
|
||||
q &&
|
||||
!String(r.name ?? "")
|
||||
.toLowerCase()
|
||||
.includes(q)
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}, [requests, statusFilter, search]);
|
||||
@@ -152,10 +174,8 @@ export function JellyseerRequestsTable({ serviceId }: { serviceId: string }) {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="open">Open</SelectItem>
|
||||
<SelectItem value="all">All</SelectItem>
|
||||
<SelectItem value="pending">Pending</SelectItem>
|
||||
<SelectItem value="approved">Approved</SelectItem>
|
||||
<SelectItem value="declined">Declined</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -197,7 +217,10 @@ export function JellyseerRequestsTable({ serviceId }: { serviceId: string }) {
|
||||
<TableRow key={String(row.original.id ?? row.index)}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user