feat(mangas): per-field sort options + direction toggle on the catalog (0.88.0)

Replace the two-option `ListSort` enum with an orthogonal sort field
(created/updated/title/author, default updated) and direction
(asc/desc, default desc). The catalog now defaults to last-updated-first
and lets the user order by any field in either direction.

Backend builds ORDER BY from the enums only (no injection seam), with a
NULLS-LAST author subquery and a stable id tie-break. Frontend adds a
direction toggle with per-field defaults (dates desc, text asc) and
labels (Newest/Oldest vs A->Z/Z->A), reusing SegmentedControl; URL state
omits the per-field defaults and validates on hydrate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-24 21:39:12 +02:00
parent 93b7e451bf
commit 1079a0151a
10 changed files with 348 additions and 64 deletions

View File

@@ -89,7 +89,7 @@ describe('mangas api client', () => {
expect(result.page).toEqual({ limit: 50, offset: 0, total: 1 });
});
it('listMangas encodes search, status, ids (csv), limit, offset, sort', async () => {
it('listMangas encodes search, status, ids (csv), limit, offset, sort, order', async () => {
fetchSpy.mockResolvedValueOnce(ok(emptyPage()));
await listMangas({
search: 'one piece',
@@ -99,7 +99,8 @@ describe('mangas api client', () => {
tagIds: ['t1', 't2'],
limit: 10,
offset: 20,
sort: 'title'
sort: 'title',
order: 'asc'
});
const url = fetchSpy.mock.calls[0][0] as string;
expect(url).toMatch(/\/v1\/mangas\?/);
@@ -113,6 +114,7 @@ describe('mangas api client', () => {
expect(url).toContain('limit=10');
expect(url).toContain('offset=20');
expect(url).toContain('sort=title');
expect(url).toContain('order=asc');
});
it('getManga returns the enriched detail shape', async () => {