feat(collections): Add library-aware filtering to collection detail pages

- Add library_id parameter to BuildSections and getViewAllURL functions
- Update dashboard handler to pass libraryID when building sections
- Add library_id query param support to collection detail page handler
- When library_id is provided, filter collection items by that library
- When no library_id, show all books (backward compatible)
- Reuses GetCollectionItemsForDashboard query for filtered results
- Preserves context when navigating from dashboard to collection detail
This commit is contained in:
2026-03-02 13:11:35 -05:00
parent 8f83403342
commit be4230266e
2 changed files with 66 additions and 21 deletions
+7 -4
View File
@@ -61,7 +61,7 @@ func (h *DashboardHandler) GetSections(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to load dashboard sections"}) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to load dashboard sections"})
} }
sectionData := BuildSections(sections) sectionData := BuildSections(sections, libraryID)
return c.JSON(http.StatusOK, map[string]interface{}{"sections": sectionData}) return c.JSON(http.StatusOK, map[string]interface{}{"sections": sectionData})
} }
@@ -154,7 +154,7 @@ func (h *DashboardHandler) RestoreSystemCollection(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"message": "System collection restored to defaults"}) return c.JSON(http.StatusOK, map[string]string{"message": "System collection restored to defaults"})
} }
func BuildSections(sections []services.DashboardSection) []SectionData { func BuildSections(sections []services.DashboardSection, currentLibraryID string) []SectionData {
var result []SectionData var result []SectionData
for _, ds := range sections { for _, ds := range sections {
@@ -177,7 +177,7 @@ func BuildSections(sections []services.DashboardSection) []SectionData {
Description: ds.Description, Description: ds.Description,
Icon: ds.Icon, Icon: ds.Icon,
Items: bookCards, Items: bookCards,
ViewAllURL: getViewAllURL(ds.CollectionID.String()), ViewAllURL: getViewAllURL(ds.CollectionID.String(), currentLibraryID),
Priority: ds.Priority, Priority: ds.Priority,
}) })
} }
@@ -185,8 +185,11 @@ func BuildSections(sections []services.DashboardSection) []SectionData {
return result return result
} }
func getViewAllURL(collectionID string) string { func getViewAllURL(collectionID string, libraryID string) string {
if collectionID != "" { if collectionID != "" {
if libraryID != "" {
return "/collections/" + collectionID + "?library_id=" + libraryID
}
return "/collections/" + collectionID return "/collections/" + collectionID
} }
return "" return ""
+59 -17
View File
@@ -191,8 +191,8 @@ func registerFrontendRoutes(cfg *Config) {
} }
} }
sectionData := handlers.BuildSections(visibleSections) sectionData := handlers.BuildSections(visibleSections, libraryID)
allSectionsData := handlers.BuildSections(allSections) allSectionsData := handlers.BuildSections(allSections, libraryID)
var buf bytes.Buffer var buf bytes.Buffer
err = templates.Dashboard(user, sectionData, allSectionsData, libData, libraryID, prefs.HiddenCollections, limit, errorMsg).Render(c.Request().Context(), &buf) err = templates.Dashboard(user, sectionData, allSectionsData, libData, libraryID, prefs.HiddenCollections, limit, errorMsg).Render(c.Request().Context(), &buf)
@@ -358,24 +358,66 @@ func registerFrontendRoutes(cfg *Config) {
} }
} }
} else { } else {
// User collection - fetch collection items // User collection - check if library_id filter is present
collItems, err := cfg.Queries.GetCollectionItems(c.Request().Context(), pgtype.UUID{Bytes: collUUID, Valid: true}) libraryID := c.QueryParam("library_id")
if err != nil {
books = []handlers.BookInfo{}
}
// Convert to BookInfo format if libraryID != "" {
bookCards := make([]handlers.BookInfo, len(collItems)) // Filter by library - reuse dashboard query
for i, item := range collItems { libUUID, err := uuid.Parse(libraryID)
itemUUID, _ := uuid.FromBytes(item.MediaItemID.Bytes[0:16]) if err != nil {
bookCards[i] = handlers.BookInfo{ return renderErrorPage(c, "Invalid library ID", "invalid_library_id")
MediaItemID: itemUUID.String(),
Title: item.Title,
Author: getText(item.Author),
CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
} }
// Use GetCollectionItemsForDashboard for library-filtered results
collItems, err := cfg.Queries.GetCollectionItemsForDashboard(c.Request().Context(),
database.GetCollectionItemsForDashboardParams{
CollectionID: pgtype.UUID{Bytes: collUUID, Valid: true},
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
Limit: 1000,
})
if err != nil {
books = []handlers.BookInfo{}
} else {
// Convert to BookInfo format (non-excluded only)
var validItems []database.GetCollectionItemsForDashboardRow
for _, item := range collItems {
if !item.Excluded.Valid || !item.Excluded.Bool {
validItems = append(validItems, item)
}
}
bookCards := make([]handlers.BookInfo, len(validItems))
for i, item := range validItems {
itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
bookCards[i] = handlers.BookInfo{
MediaItemID: itemUUID.String(),
Title: item.Title,
Author: getText(item.Author),
CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
}
}
books = bookCards
}
} else {
// No library filter - show all books in collection
collItems, err := cfg.Queries.GetCollectionItems(c.Request().Context(), pgtype.UUID{Bytes: collUUID, Valid: true})
if err != nil {
books = []handlers.BookInfo{}
}
// Convert to BookInfo format
bookCards := make([]handlers.BookInfo, len(collItems))
for i, item := range collItems {
itemUUID, _ := uuid.FromBytes(item.MediaItemID.Bytes[0:16])
bookCards[i] = handlers.BookInfo{
MediaItemID: itemUUID.String(),
Title: item.Title,
Author: getText(item.Author),
CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
}
}
books = bookCards
} }
books = bookCards
} }
// Build collection data // Build collection data
colData := templates.CollectionData{ colData := templates.CollectionData{