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:
@@ -40,6 +40,9 @@ func (h *DashboardHandler) GetSections(c echo.Context) error {
|
||||
prefs, _ := h.dashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID)
|
||||
|
||||
limit := 20
|
||||
if prefs.ItemsPerSection.Valid {
|
||||
limit = int(prefs.ItemsPerSection.Int32)
|
||||
}
|
||||
if limitStr := c.QueryParam("limit"); limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
|
||||
limit = l
|
||||
@@ -83,6 +86,11 @@ func (h *DashboardHandler) UpdatePreferences(c echo.Context) error {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
|
||||
}
|
||||
|
||||
// Sanitize preferences (remove duplicates)
|
||||
cleanHidden, cleanOrder := h.dashboardService.SanitizeDashboardPreferences(req.HiddenCollections, req.CollectionOrder)
|
||||
req.HiddenCollections = cleanHidden
|
||||
req.CollectionOrder = cleanOrder
|
||||
|
||||
prefs, err := h.dashboardService.UpsertDashboardPreferences(c.Request().Context(), database.UpsertDashboardPreferencesParams{
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
|
||||
@@ -157,29 +165,49 @@ func BuildSections(sections []services.DashboardSection) []SectionData {
|
||||
}
|
||||
|
||||
result = append(result, SectionData{
|
||||
ID: ds.CollectionName,
|
||||
IsSystem: ds.IsSystem,
|
||||
Title: ds.Title,
|
||||
Description: ds.Description,
|
||||
Icon: ds.Icon,
|
||||
Items: bookCards,
|
||||
ViewAllURL: getViewAllURL(ds.CollectionName, ds.QueryType),
|
||||
Priority: ds.Priority,
|
||||
ID: ds.CollectionName,
|
||||
CollectionID: ds.CollectionID.String(),
|
||||
IsSystem: ds.IsSystem,
|
||||
Title: ds.Title,
|
||||
Description: ds.Description,
|
||||
Icon: ds.Icon,
|
||||
Items: bookCards,
|
||||
ViewAllURL: getViewAllURL(ds.CollectionID.String()),
|
||||
Priority: ds.Priority,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getViewAllURL(key, queryType string) string {
|
||||
urls := map[string]string{
|
||||
"continue-reading": "/section/continue-reading",
|
||||
"recently-added": "/section/recently-added",
|
||||
"recently-read": "/history",
|
||||
"not-started": "/section/not-started",
|
||||
}
|
||||
if url, exists := urls[queryType]; exists {
|
||||
return url
|
||||
func getViewAllURL(collectionID string) string {
|
||||
if collectionID != "" {
|
||||
return "/collections/" + collectionID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *DashboardHandler) GetPreferences(c echo.Context) error {
|
||||
user := c.Get("user").(database.Users)
|
||||
userUUID := uuid.UUID(user.ID.Bytes)
|
||||
libraryID := c.QueryParam("library_id")
|
||||
|
||||
// ✅ Add validation
|
||||
if libraryID == "" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "library_id required"})
|
||||
}
|
||||
|
||||
libUUID, err := uuid.Parse(libraryID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
|
||||
}
|
||||
prefs, err := h.dashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": "Preferences not found"})
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"hidden_collections": prefs.HiddenCollections,
|
||||
"collection_order": prefs.CollectionOrder,
|
||||
"items_per_section": prefs.ItemsPerSection.Int32,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user