From 480eb92f7dde67d6145a028f1d23ebf15e3739f4 Mon Sep 17 00:00:00 2001 From: "aditya.chandel" Date: Thu, 10 Jul 2025 09:34:38 -0600 Subject: [PATCH] =?UTF-8?q?Fix=20read=20status=20filter=20showing=20only?= =?UTF-8?q?=20=E2=80=9CUnknown=E2=80=9D=20with=20no=20results?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../booklore/service/BookService.java | 28 ++++++++++++------- .../book-filter/book-filter.component.ts | 19 +++++++++---- .../book-browser/filters/SidebarFilter.ts | 8 +++++- .../metadata-viewer.component.ts | 2 +- booklore-ui/src/app/book/model/book.model.ts | 3 +- .../src/app/book/service/book.service.ts | 17 ++++++++--- 6 files changed, 54 insertions(+), 23 deletions(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java index c492a9c3a..3dc3176b5 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java @@ -58,23 +58,31 @@ public class BookService { public List getBookDTOs(boolean includeDescription) { BookLoreUser user = authenticationService.getAuthenticatedUser(); boolean isAdmin = user.getPermissions().isAdmin(); - List books; - if (isAdmin) { - books = bookQueryService.getAllBooks(includeDescription); - } else { - Set libraryIds = user.getAssignedLibraries().stream() - .map(Library::getId) - .collect(Collectors.toSet()); - books = bookQueryService.getAllBooksByLibraryIds(libraryIds, includeDescription); - } - Map progressMap = userProgressService.fetchUserProgress(user.getId(), books.stream().map(Book::getId).collect(Collectors.toSet())); + + List books = isAdmin + ? bookQueryService.getAllBooks(includeDescription) + : bookQueryService.getAllBooksByLibraryIds( + user.getAssignedLibraries().stream() + .map(Library::getId) + .collect(Collectors.toSet()), + includeDescription + ); + + Map progressMap = + userProgressService.fetchUserProgress( + user.getId(), + books.stream().map(Book::getId).collect(Collectors.toSet()) + ); + books.forEach(book -> { UserBookProgressEntity progress = progressMap.get(book.getId()); if (progress != null) { setBookProgress(book, progress); book.setLastReadTime(progress.getLastReadTime()); + book.setReadStatus(String.valueOf(progress.getReadStatus())); } }); + return books; } diff --git a/booklore-ui/src/app/book/components/book-browser/book-filter/book-filter.component.ts b/booklore-ui/src/app/book/components/book-browser/book-filter/book-filter.component.ts index 1201e2061..7fbf16544 100644 --- a/booklore-ui/src/app/book/components/book-browser/book-filter/book-filter.component.ts +++ b/booklore-ui/src/app/book/components/book-browser/book-filter/book-filter.component.ts @@ -1,6 +1,6 @@ import {Component, EventEmitter, inject, Input, OnDestroy, OnInit, Output} from '@angular/core'; -import {BehaviorSubject, combineLatest, Observable, of, Subject, takeUntil} from 'rxjs'; -import {distinctUntilChanged, filter, map, take} from 'rxjs/operators'; +import {combineLatest, Observable, of, Subject, takeUntil} from 'rxjs'; +import {distinctUntilChanged, map} from 'rxjs/operators'; import {BookService} from '../../../service/book.service'; import {Library} from '../../../model/library.model'; import {Shelf} from '../../../model/shelf.model'; @@ -25,7 +25,7 @@ export const ratingRanges = [ {id: '4.5plus', label: '4.5+', min: 4.5, max: Infinity, sortIndex: 5} ]; -export const ratingOptions10 = Array.from({ length: 10 }, (_, i) => ({ +export const ratingOptions10 = Array.from({length: 10}, (_, i) => ({ id: `${i + 1}`, label: `${i + 1}`, value: i + 1, @@ -83,7 +83,7 @@ function getRatingRangeFilters(rating?: number): { id: string; name: string; sor function getRatingRangeFilters10(rating?: number): { id: string; name: string; sortIndex?: number }[] { if (!rating || rating < 1 || rating > 10) return []; const idx = ratingOptions10.find(r => r.value === rating || +r.id === rating); - return idx ? [{ id: idx.id, name: idx.label, sortIndex: idx.sortIndex }] : []; + return idx ? [{id: idx.id, name: idx.label, sortIndex: idx.sortIndex}] : []; } function extractPublishedYearFilter(book: Book): { id: number; name: string }[] { @@ -119,10 +119,11 @@ const readStatusLabels: Record = { [ReadStatus.READ]: 'Read', [ReadStatus.WONT_READ]: 'Won’t Read', [ReadStatus.ABANDONED]: 'Abandoned', + [ReadStatus.UNSET]: 'Unset' }; function getReadStatusName(status?: ReadStatus | null): string { - return status != null ? readStatusLabels[status] ?? 'Unknown' : 'Unknown'; + return status != null ? readStatusLabels[status] ?? 'Unset' : 'Unset'; } @Component({ @@ -198,7 +199,13 @@ export class BookFilterComponent implements OnInit, OnDestroy { category: this.getFilterStream((book: Book) => book.metadata?.categories!.map(name => ({id: name, name})) || [], 'id', 'name', sortMode), series: this.getFilterStream((book) => (book.metadata?.seriesName ? [{id: book.metadata.seriesName, name: book.metadata.seriesName}] : []), 'id', 'name', sortMode), publisher: this.getFilterStream((book) => (book.metadata?.publisher ? [{id: book.metadata.publisher, name: book.metadata.publisher}] : []), 'id', 'name', sortMode), - readStatus: this.getFilterStream((book: Book) => [{id: book.readStatus ?? ReadStatus.UNREAD, name: getReadStatusName(book.readStatus)}], 'id', 'name', sortMode), + readStatus: this.getFilterStream((book: Book) => { + let status = book.readStatus; + if (status == null || !(status in readStatusLabels)) { + status = ReadStatus.UNSET; + } + return [{id: status, name: getReadStatusName(status)}]; + }, 'id', 'name', sortMode), matchScore: this.getFilterStream((book: Book) => getMatchScoreRangeFilters(book.metadataMatchScore), 'id', 'name', 'sortIndex'), personalRating: this.getFilterStream((book: Book) => getRatingRangeFilters10(book.metadata?.personalRating!), 'id', 'name', 'sortIndex'), amazonRating: this.getFilterStream((book: Book) => getRatingRangeFilters(book.metadata?.amazonRating!), 'id', 'name', 'sortIndex'), diff --git a/booklore-ui/src/app/book/components/book-browser/filters/SidebarFilter.ts b/booklore-ui/src/app/book/components/book-browser/filters/SidebarFilter.ts index 077d869f6..2fa5ba94d 100644 --- a/booklore-ui/src/app/book/components/book-browser/filters/SidebarFilter.ts +++ b/booklore-ui/src/app/book/components/book-browser/filters/SidebarFilter.ts @@ -3,6 +3,7 @@ import {map} from 'rxjs/operators'; import {BookFilter} from './BookFilter'; import {BookState} from '../../../model/state/book-state.model'; import {fileSizeRanges, matchScoreRanges, pageCountRanges, ratingRanges} from '../book-filter/book-filter.component'; +import {Book, ReadStatus} from '../../../model/book.model'; export function isRatingInRange(rating: number | undefined | null, rangeId: string): boolean { if (rating == null) return false; @@ -37,6 +38,11 @@ export function isMatchScoreInRange(score: number | undefined | null, rangeId: s return score >= range.min && score < range.max; } +export function doesBookMatchReadStatus(book: Book, selected: string[]): boolean { + const status = book.readStatus ?? ReadStatus.UNSET; + return selected.includes(status); +} + export class SideBarFilter implements BookFilter { constructor(private selectedFilter$: Observable, private selectedFilterMode$: Observable<'and' | 'or'>) { @@ -69,7 +75,7 @@ export class SideBarFilter implements BookFilter { ? filterValues.every(val => book.metadata?.seriesName === val) : filterValues.some(val => book.metadata?.seriesName === val); case 'readStatus': - return filterValues.some(val => book.readStatus === val); + return doesBookMatchReadStatus(book, filterValues); case 'amazonRating': return filterValues.some(range => isRatingInRange(book.metadata?.amazonRating, range)); case 'goodreadsRating': diff --git a/booklore-ui/src/app/book/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts b/booklore-ui/src/app/book/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts index 864c7905f..0e54690cf 100644 --- a/booklore-ui/src/app/book/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts +++ b/booklore-ui/src/app/book/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts @@ -466,7 +466,7 @@ export class MetadataViewerComponent implements OnInit, OnChanges { })); getStatusLabel(value: string): string { - return this.readStatusOptions.find(o => o.value === value)?.label.toUpperCase() ?? 'N/A'; + return this.readStatusOptions.find(o => o.value === value)?.label.toUpperCase() ?? 'UNSET'; } updateReadStatus(status: ReadStatus): void { diff --git a/booklore-ui/src/app/book/model/book.model.ts b/booklore-ui/src/app/book/model/book.model.ts index e626981e6..6724409ff 100644 --- a/booklore-ui/src/app/book/model/book.model.ts +++ b/booklore-ui/src/app/book/model/book.model.ts @@ -222,5 +222,6 @@ export enum ReadStatus { PARTIALLY_READ = 'PARTIALLY_READ', PAUSED = 'PAUSED', WONT_READ = 'WONT_READ', - ABANDONED = 'ABANDONED' + ABANDONED = 'ABANDONED', + UNSET = 'UNSET' } diff --git a/booklore-ui/src/app/book/service/book.service.ts b/booklore-ui/src/app/book/service/book.service.ts index b75631d69..9b4a12208 100644 --- a/booklore-ui/src/app/book/service/book.service.ts +++ b/booklore-ui/src/app/book/service/book.service.ts @@ -403,6 +403,19 @@ export class BookService { ); } + updateBookReadStatus(id: number, status: ReadStatus): Observable { + return this.http.put(`${this.url}/${id}/read-status`, {status}).pipe( + tap(() => { + const currentState = this.bookStateSubject.value; + if (!currentState.books) return; + const updatedBooks = currentState.books.map(book => + book.id === id ? {...book, readStatus: status} : book + ); + this.bookStateSubject.next({...currentState, books: updatedBooks}); + }) + ); + } + /*------------------ All the websocket handlers go below ------------------*/ @@ -506,8 +519,4 @@ export class BookService { getBackupMetadata(bookId: number) { return this.http.get(`${this.url}/${bookId}/metadata/restore`); } - - updateBookReadStatus(id: number, status: ReadStatus): Observable { - return this.http.put(`${this.url}/${id}/read-status`, {status}); - } }