feat(library): sort control on the pressings grid
Add a Sort dropdown to /library next to the filter: Recently added (default, importedAt desc — the prior order), Release date (album year, newest first, unknown-year last), Album A–Z, Artist A–Z. Client-side sort of the already-fetched albums; importedAt was already on the route, just declared on the client type now. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ type Album = {
|
|||||||
album: string;
|
album: string;
|
||||||
format: string;
|
format: string;
|
||||||
qualityClass: number;
|
qualityClass: number;
|
||||||
|
importedAt: string;
|
||||||
rgMbid: string | null;
|
rgMbid: string | null;
|
||||||
year: string | null;
|
year: string | null;
|
||||||
primaryType: string | null;
|
primaryType: string | null;
|
||||||
@@ -21,9 +22,19 @@ type Album = {
|
|||||||
trackNames: string[];
|
trackNames: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SortKey = "added" | "release" | "album" | "artist";
|
||||||
|
const SORTS: { key: SortKey; label: string; cmp: (a: Album, b: Album) => number }[] = [
|
||||||
|
{ key: "added", label: "Recently added", cmp: (a, b) => b.importedAt.localeCompare(a.importedAt) },
|
||||||
|
// Release date, newest first; albums with an unknown year sort to the bottom.
|
||||||
|
{ key: "release", label: "Release date", cmp: (a, b) => (b.year ?? "0000").localeCompare(a.year ?? "0000") || a.album.localeCompare(b.album) },
|
||||||
|
{ key: "album", label: "Album A–Z", cmp: (a, b) => a.album.localeCompare(b.album) },
|
||||||
|
{ key: "artist", label: "Artist A–Z", cmp: (a, b) => a.artist.localeCompare(b.artist) || (a.year ?? "").localeCompare(b.year ?? "") },
|
||||||
|
];
|
||||||
|
|
||||||
export function LibraryClient() {
|
export function LibraryClient() {
|
||||||
const [albums, setAlbums] = useState<Album[]>([]);
|
const [albums, setAlbums] = useState<Album[]>([]);
|
||||||
const [q, setQ] = useState("");
|
const [q, setQ] = useState("");
|
||||||
|
const [sort, setSort] = useState<SortKey>("added");
|
||||||
const [open, setOpen] = useState<Album | null>(null);
|
const [open, setOpen] = useState<Album | null>(null);
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
@@ -56,9 +67,12 @@ export function LibraryClient() {
|
|||||||
|
|
||||||
const shown = useMemo(() => {
|
const shown = useMemo(() => {
|
||||||
const needle = q.trim().toLowerCase();
|
const needle = q.trim().toLowerCase();
|
||||||
if (!needle) return albums;
|
const filtered = needle
|
||||||
return albums.filter((a) => `${a.artist} ${a.album}`.toLowerCase().includes(needle));
|
? albums.filter((a) => `${a.artist} ${a.album}`.toLowerCase().includes(needle))
|
||||||
}, [albums, q]);
|
: albums;
|
||||||
|
const cmp = (SORTS.find((s) => s.key === sort) ?? SORTS[0]).cmp;
|
||||||
|
return [...filtered].sort(cmp);
|
||||||
|
}, [albums, q, sort]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -70,6 +84,14 @@ export function LibraryClient() {
|
|||||||
<span>Filter</span>
|
<span>Filter</span>
|
||||||
<input aria-label="filter library" placeholder="Artist or album…" value={q} onChange={(e) => setQ(e.target.value)} />
|
<input aria-label="filter library" placeholder="Artist or album…" value={q} onChange={(e) => setQ(e.target.value)} />
|
||||||
</label>
|
</label>
|
||||||
|
<label className="field">
|
||||||
|
<span>Sort</span>
|
||||||
|
<select aria-label="sort library" 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>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{albums.length === 0 ? (
|
{albums.length === 0 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user