feat(sections): configurable sort for watchlist/favorites sections
Watchlist and favorites sections keep their stored list order by default (provider-synced positions first, then newest-added). An optional sort/order config now supports title, release date, IMDb rating, and date-added-to-list ordering, applied consistently on the home rail and the catalog "see all" page. added_at is resolved from the list entries via a shared OrderPersonalListIDs helper since it is distinct from the catalog's library added_at. The section editor gains a Sort dropdown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -117,14 +117,36 @@ interface ParamFieldProps {
|
||||
onChange: (next: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
// Sort choices for watchlist/favorites sections. The empty value keeps the
|
||||
// list's stored order (provider sync order, then newest-added first).
|
||||
const PERSONAL_LIST_SORT_OPTIONS = [
|
||||
{ value: "", label: "List order (default)" },
|
||||
{ value: "added_at:desc", label: "Date added (newest first)" },
|
||||
{ value: "added_at:asc", label: "Date added (oldest first)" },
|
||||
{ value: "title:asc", label: "Title (A–Z)" },
|
||||
{ value: "title:desc", label: "Title (Z–A)" },
|
||||
{ value: "release_date:desc", label: "Release date (newest first)" },
|
||||
{ value: "release_date:asc", label: "Release date (oldest first)" },
|
||||
{ value: "rating_imdb:desc", label: "IMDb rating (highest first)" },
|
||||
];
|
||||
|
||||
// PersonalListFilterFields edits the optional filter_type / filter_library_ids
|
||||
// filters for watchlist and favorites sections, e.g. a "Movies watchlist" rail.
|
||||
// filters and sort for watchlist and favorites sections, e.g. a "Movies
|
||||
// watchlist" rail sorted by release date.
|
||||
function PersonalListFilterFields({ params, onChange }: ParamFieldProps) {
|
||||
const { data: libraries } = useAvailableUserLibraries();
|
||||
const filterType = typeof params.filter_type === "string" ? params.filter_type : "";
|
||||
const libraryIds = Array.isArray(params.filter_library_ids)
|
||||
? params.filter_library_ids.filter((id): id is number => typeof id === "number")
|
||||
: [];
|
||||
const sortField = typeof params.sort === "string" ? params.sort : "";
|
||||
const sortOrder =
|
||||
typeof params.order === "string" && params.order
|
||||
? params.order
|
||||
: sortField === "title"
|
||||
? "asc" // mirror the backend's per-field default order
|
||||
: "desc";
|
||||
const sortValue = sortField ? `${sortField}:${sortOrder}` : "";
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
@@ -156,6 +178,23 @@ function PersonalListFilterFields({ params, onChange }: ParamFieldProps) {
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="block md:col-span-2">
|
||||
<span className="mb-1 block text-xs text-white/70">Sort</span>
|
||||
<select
|
||||
value={PERSONAL_LIST_SORT_OPTIONS.some((o) => o.value === sortValue) ? sortValue : ""}
|
||||
onChange={(e) => {
|
||||
const [sort, order] = e.target.value.split(":");
|
||||
onChange({ ...params, sort: sort || undefined, order: order || undefined });
|
||||
}}
|
||||
className="w-full rounded border border-white/15 bg-white/5 px-3 py-2 text-sm"
|
||||
>
|
||||
{PERSONAL_LIST_SORT_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user