feat(artists): sort control on the roster
Add a Sort dropdown to /artists next to the filter: Recently followed (default, createdAt desc — the prior order), Name A–Z, Most in library, Most monitored, Most releases. Client-side sort of the already-fetched roster; the filter box and "In library, not followed" section are unchanged. Exposes WatchedArtist.createdAt on /api/artists for the sort. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ export async function GET(request: Request) {
|
||||
id: a.id,
|
||||
mbid: a.mbid,
|
||||
name: a.name,
|
||||
createdAt: a.createdAt,
|
||||
autoMonitorFuture: a.autoMonitorFuture,
|
||||
showAllTypes: a.showAllTypes,
|
||||
releaseCount: visible.length,
|
||||
|
||||
@@ -10,6 +10,7 @@ type Artist = {
|
||||
id: string;
|
||||
mbid: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
autoMonitorFuture: boolean;
|
||||
releaseCount: number;
|
||||
monitoredCount: number;
|
||||
@@ -17,11 +18,21 @@ type Artist = {
|
||||
};
|
||||
type Hit = { mbid: string; name: string; disambiguation: string };
|
||||
|
||||
type SortKey = "followed" | "name" | "have" | "monitored" | "releases";
|
||||
const SORTS: { key: SortKey; label: string; cmp: (a: Artist, b: Artist) => number }[] = [
|
||||
{ key: "followed", label: "Recently followed", cmp: (a, b) => b.createdAt.localeCompare(a.createdAt) },
|
||||
{ key: "name", label: "Name A–Z", cmp: (a, b) => a.name.localeCompare(b.name) },
|
||||
{ key: "have", label: "Most in library", cmp: (a, b) => b.haveCount - a.haveCount || a.name.localeCompare(b.name) },
|
||||
{ key: "monitored", label: "Most monitored", cmp: (a, b) => b.monitoredCount - a.monitoredCount || a.name.localeCompare(b.name) },
|
||||
{ key: "releases", label: "Most releases", cmp: (a, b) => b.releaseCount - a.releaseCount || a.name.localeCompare(b.name) },
|
||||
];
|
||||
|
||||
export function ArtistsClient() {
|
||||
const [artists, setArtists] = useState<Artist[]>([]);
|
||||
const [ownedNotFollowed, setOwnedNotFollowed] = useState<string[]>([]);
|
||||
const [justFollowed, setJustFollowed] = useState<Set<string>>(new Set());
|
||||
const [filter, setFilter] = useState("");
|
||||
const [sort, setSort] = useState<SortKey>("followed");
|
||||
const [showAdd, setShowAdd] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [hits, setHits] = useState<Hit[]>([]);
|
||||
@@ -113,8 +124,10 @@ export function ArtistsClient() {
|
||||
const followedMbids = new Set(artists.map((a) => a.mbid));
|
||||
const shown = useMemo(() => {
|
||||
const n = filter.trim().toLowerCase();
|
||||
return n ? artists.filter((a) => a.name.toLowerCase().includes(n)) : artists;
|
||||
}, [artists, filter]);
|
||||
const filtered = n ? artists.filter((a) => a.name.toLowerCase().includes(n)) : artists;
|
||||
const cmp = (SORTS.find((s) => s.key === sort) ?? SORTS[0]).cmp;
|
||||
return [...filtered].sort(cmp);
|
||||
}, [artists, filter, sort]);
|
||||
const ownedShown = useMemo(
|
||||
() => ownedNotFollowed.filter((name) => !justFollowed.has(name)),
|
||||
[ownedNotFollowed, justFollowed],
|
||||
@@ -137,6 +150,14 @@ export function ArtistsClient() {
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Sort</span>
|
||||
<select aria-label="sort artists" value={sort} onChange={(e) => setSort(e.target.value as SortKey)}>
|
||||
{SORTS.map((s) => (
|
||||
<option key={s.key} value={s.key}>{s.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<SectionHeader
|
||||
|
||||
Reference in New Issue
Block a user