add library filter panel and tag selector enhancements

- Add left sidebar filter panel to MixedView and GamesView with name
  search and tag toggles; only shows tags/categories used in the current
  library; AND logic when multiple tags are selected
- Add GET /api/tags/library-assignments endpoint returning all tag
  assignments for a library keyed by mediaKey
- Add getTagAssignmentsForLibrary() and getTagsSortedByUsage() to tags lib
- Support ?sort=usage on GET /api/tags/items to order by assignment count
- Tag selector: per-category search, top-25-by-usage display, inline add
  tag (auto-assigned to current item) and add category flows
- Tag selector: group assigned tags by category into nested pills
- Fix nested <button> hydration error in EntryTile (outer element is now
  a div with role="button")
- Keep filter panel assignments in sync when tags are toggled or created

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 19:50:28 -04:00
parent 75fe82f0de
commit 43436f5cae
8 changed files with 643 additions and 122 deletions

View File

@@ -1,11 +1,14 @@
import { NextRequest, NextResponse } from 'next/server'
import { getTags, addTag } from '@/lib/tags'
import { getTags, getTagsSortedByUsage, addTag } from '@/lib/tags'
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const categoryId = searchParams.get('categoryId') ?? undefined
return NextResponse.json(getTags(categoryId))
const sort = searchParams.get('sort')
return NextResponse.json(
sort === 'usage' ? getTagsSortedByUsage(categoryId) : getTags(categoryId)
)
} catch (err) {
return NextResponse.json({ error: (err as Error).message }, { status: 500 })
}

View File

@@ -0,0 +1,8 @@
import { getTagAssignmentsForLibrary } from '@/lib/tags'
export async function GET(req: Request) {
const { searchParams } = new URL(req.url)
const libraryId = searchParams.get('libraryId')
if (!libraryId) return Response.json({ error: 'libraryId required' }, { status: 400 })
return Response.json(getTagAssignmentsForLibrary(libraryId))
}