diff --git a/UNIFIED_SEARCH_IMPLEMENTATION.md b/UNIFIED_SEARCH_IMPLEMENTATION.md index 9882e70..db4a464 100644 --- a/UNIFIED_SEARCH_IMPLEMENTATION.md +++ b/UNIFIED_SEARCH_IMPLEMENTATION.md @@ -707,295 +707,567 @@ hx-get="/api/media-items/search?library_id={ currentLibraryID }&limit={ limit }& **Note:** Change parameter name from `search` to `q` to match backend handler expectation. -#### 4.3 Add Autocomplete Dropdowns with Search Button +#### 4.3 COMPLETE IMPLEMENTATION - All Fixes in One Section + +**IMPORTANT:** This section contains ALL code changes needed to preserve your sort/save/load features while adding autocomplete. Implement everything in this section only - no need to modify earlier phases. + +--- + +### Step 1: Update SQL Query to Support Sort Parameter + +**File:** `internal/database/queries/queries.sql` + +**Find the `SearchMediaItemsUnified` query (you added this in Phase 2) and replace the ORDER BY clause:** + +**BEFORE:** +```sql +ORDER BY + CASE + WHEN sqlc.narg('search_query') != '' THEN + GREATEST(...) + ELSE 0 + END DESC, + mi.title ASC +``` + +**AFTER:** +```sql +ORDER BY + -- Primary sort: relevance score when searching + CASE + WHEN sqlc.narg('search_query') != '' THEN + GREATEST( + CASE WHEN sqlc.narg('is_exact_search') = false THEN + word_similarity(sqlc.narg('search_query'), mi.title) + ELSE 0 END, + CASE WHEN sqlc.narg('is_exact_search') = false THEN + word_similarity(sqlc.narg('search_query'), COALESCE(mi.author, '')) + ELSE 0 END, + word_similarity(sqlc.narg('author_filter'), COALESCE(mi.author, '')), + word_similarity(sqlc.narg('genre_filter'), COALESCE(mi.genre, '')) + ) + ELSE 0 + END DESC, + -- Secondary sort: user-specified sort parameter + CASE + WHEN sqlc.narg('sort') = 'title ASC' THEN mi.title + ELSE '' + END ASC, + CASE + WHEN sqlc.narg('sort') = 'title DESC' THEN mi.title + ELSE '' + END DESC, + CASE + WHEN sqlc.narg('sort') = 'author ASC' THEN COALESCE(mi.author, '') + ELSE '' + END ASC, + CASE + WHEN sqlc.narg('sort') = 'author DESC' THEN COALESCE(mi.author, '') + ELSE '' + END DESC, + CASE + WHEN sqlc.narg('sort') = 'created_at ASC' THEN mi.created_at + ELSE '1970-01-01'::timestamp + END ASC, + CASE + WHEN sqlc.narg('sort') = 'created_at DESC' THEN mi.created_at + ELSE '1970-01-01'::timestamp + END DESC, + CASE + WHEN sqlc.narg('sort') = 'page_count ASC' THEN COALESCE(mi.page_count::text, '0') + ELSE '' + END ASC, + CASE + WHEN sqlc.narg('sort') = 'page_count DESC' THEN COALESCE(mi.page_count::text, '0') + ELSE '' + END DESC, + -- Tertiary sort: title (default fallback) + mi.title ASC +``` + +**Regenerate Go code:** +```bash +sqlc generate +``` + +--- + +### Step 2: Update Search Service to Support Sort + +**File:** `internal/services/search.go` + +**Add `Sort` field to `SearchParams` struct:** + +```go +// SearchParams contains parameters for unified search +type SearchParams struct { + UserID pgtype.UUID + LibraryID pgtype.UUID + AuthorFilter string + SeriesFilter string + GenreFilter string + LanguageFilter string + YearMin int + YearMax int + HasCover bool + SearchQuery string + Sort string // ADD THIS LINE + Limit int + Offset int +} +``` + +**Update the database parameters in `SearchMediaItemsUnified` method:** + +```go +// Build database parameters +dbParams := database.SearchMediaItemsUnifiedParams{ + UserID: params.UserID, + LibraryID: params.LibraryID, + AuthorFilter: pgtype.Text{String: params.AuthorFilter, Valid: true}, + SeriesFilter: pgtype.Text{String: params.SeriesFilter, Valid: true}, + GenreFilter: pgtype.Text{String: params.GenreFilter, Valid: true}, + LanguageFilter: pgtype.Text{String: params.LanguageFilter, Valid: true}, + YearMin: pgtype.Int4{Int32: int32(params.YearMin), Valid: true}, + YearMax: pgtype.Int4{Int32: int32(params.YearMax), Valid: true}, + HasCover: pgtype.Bool{Bool: params.HasCover, Valid: true}, + SearchQuery: pgtype.Text{String: searchQuery, Valid: true}, + IsExactSearch: pgtype.Bool{Bool: isExact, Valid: true}, + SearchPattern: pgtype.Text{String: searchPattern, Valid: isExact}, + Sort: pgtype.Text{String: params.Sort, Valid: true}, // ADD THIS LINE + Limit: pgtype.Int4{Int32: int32(params.Limit), Valid: true}, + Offset: pgtype.Int4{Int32: int32(params.Offset), Valid: true}, +} +``` + +--- + +### Step 3: Update Handler to Extract Sort Parameter + +**File:** `internal/handlers/media.go` + +**In the `SearchMediaItems` handler (you added this in Phase 4.1), add sort parameter extraction:** + +**Find this section:** +```go +hasCover := c.QueryParam("has_cover") == "true" + +// Build search params +params := services.SearchParams{ +``` + +**Replace with:** +```go +hasCover := c.QueryParam("has_cover") == "true" + +// Extract sort parameter +sortParam := c.QueryParam("sort") +if sortParam == "" { + sortParam = "title ASC" // Default sort +} + +// Build search params +params := services.SearchParams{ +``` + +**Then add `Sort` to the params struct:** +```go +params := services.SearchParams{ + UserID: userID.ID, + LibraryID: libUUID, + SearchQuery: query, + AuthorFilter: authorFilter, + SeriesFilter: seriesFilter, + GenreFilter: genreFilter, + LanguageFilter: languageFilter, + YearMin: yearMin, + YearMax: yearMax, + HasCover: hasCover, + Sort: sortParam, // ADD THIS LINE + Limit: limit, + Offset: offset, +} +``` + +--- + +### Step 4: Update Template with All Filters + Autocomplete **File:** `templates/bookshelf.templ` -**Replace filter form section (includes all filters) with (lines 40-262):** +**Replace lines 83-262 (from Search input through end of filter bar) with:** ```html - -
+ +