feat: Add collection detail page with /collections/:id route

Add comprehensive collection detail page that works for both system collections
(continue-reading, recently-added, not-started) and user collections.

Backend changes:
- Add new /collections/:id route in internal/router/frontend.go
  - Fetches collection using GetCollection with UUID parameter
  - Determines collection type from QueryType field
  - Resolves library_id for system collections
  - Converts database.MediaItems to handlers.BookInfo for display
  - Renders CollectionDetail template with collection and books data

- Update SectionData struct in internal/handlers/collections.go
  - Add CollectionID string field for view all links

- Update BuildSections() in internal/handlers/dashboard.go
  - Pass CollectionID to SectionData for proper link generation

- Simplify getViewAllURL() in internal/handlers/dashboard.go
  - Return /collections/{collectionID} instead of /section/{type}
  - Works uniformly for both system and user collections

Frontend changes:
- Fix CollectionDetail template in templates/collections.templ
  - Fix broken div nesting causing compilation error
  - Add null check for CoverImagePath to prevent broken images
  - Update aspect ratio to modern aspect-[3/4] syntax
  - Use responsive widths (w-16 sm:w-20) for mobile/desktop
  - Improve card layout with horizontal flex structure
  - Add placeholder image fallback for books without covers
  - Remove erroneous renderBooks() function call

This change aligns with the backend update where system collections are
now pre-made user collections in the database with query_type fields.
All collections can now use the same CollectionDetail template for a
consistent viewing experience.
This commit is contained in:
2026-03-01 00:28:54 -05:00
parent fd608f3e3f
commit 0b666f3fdd
6 changed files with 481 additions and 274 deletions
+26 -2
View File
@@ -196,7 +196,7 @@ func (s *DashboardService) GetDashboardSections(
})
}
results = s.filterHiddenCollections(results, hiddenCollections)
results = s.FilterHiddenCollections(results, hiddenCollections)
results = s.reorderCollections(results, collectionOrder)
if len(collectionOrder) == 0 {
@@ -206,7 +206,7 @@ func (s *DashboardService) GetDashboardSections(
return results, nil
}
func (s *DashboardService) filterHiddenCollections(sections []DashboardSection, hidden []string) []DashboardSection {
func (s *DashboardService) FilterHiddenCollections(sections []DashboardSection, hidden []string) []DashboardSection {
if len(hidden) == 0 {
return sections
}
@@ -373,6 +373,30 @@ func (s *DashboardService) UpsertDashboardPreferences(ctx context.Context, param
return s.db.UpsertDashboardPreferences(ctx, params)
}
func (s *DashboardService) SanitizeDashboardPreferences(hiddenCollections, collectionOrder []string) ([]string, []string) {
// Deduplicate collection_order while preserving order
seen := make(map[string]bool)
var sanitizedOrder []string
for _, id := range collectionOrder {
if !seen[id] {
seen[id] = true
sanitizedOrder = append(sanitizedOrder, id)
}
}
// Deduplicate hidden_collections
seen = make(map[string]bool)
var sanitizedHidden []string
for _, id := range hiddenCollections {
if !seen[id] {
seen[id] = true
sanitizedHidden = append(sanitizedHidden, id)
}
}
return sanitizedHidden, sanitizedOrder
}
func (s *DashboardService) RestoreSystemCollection(ctx context.Context, userID uuid.UUID, collectionName string, resetType string) error {
defaultMetadata := map[string]struct {
Description string