Fix read status filter showing only “Unknown” with no results

This commit is contained in:
aditya.chandel
2025-07-10 09:34:38 -06:00
committed by Aditya Chandel
parent 538854b397
commit 480eb92f7d
6 changed files with 54 additions and 23 deletions

View File

@ -58,23 +58,31 @@ public class BookService {
public List<Book> getBookDTOs(boolean includeDescription) {
BookLoreUser user = authenticationService.getAuthenticatedUser();
boolean isAdmin = user.getPermissions().isAdmin();
List<Book> books;
if (isAdmin) {
books = bookQueryService.getAllBooks(includeDescription);
} else {
Set<Long> libraryIds = user.getAssignedLibraries().stream()
.map(Library::getId)
.collect(Collectors.toSet());
books = bookQueryService.getAllBooksByLibraryIds(libraryIds, includeDescription);
}
Map<Long, UserBookProgressEntity> progressMap = userProgressService.fetchUserProgress(user.getId(), books.stream().map(Book::getId).collect(Collectors.toSet()));
List<Book> books = isAdmin
? bookQueryService.getAllBooks(includeDescription)
: bookQueryService.getAllBooksByLibraryIds(
user.getAssignedLibraries().stream()
.map(Library::getId)
.collect(Collectors.toSet()),
includeDescription
);
Map<Long, UserBookProgressEntity> 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;
}

View File

@ -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, string> = {
[ReadStatus.READ]: 'Read',
[ReadStatus.WONT_READ]: 'Wont 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'),

View File

@ -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<any>, 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':

View File

@ -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 {

View File

@ -222,5 +222,6 @@ export enum ReadStatus {
PARTIALLY_READ = 'PARTIALLY_READ',
PAUSED = 'PAUSED',
WONT_READ = 'WONT_READ',
ABANDONED = 'ABANDONED'
ABANDONED = 'ABANDONED',
UNSET = 'UNSET'
}

View File

@ -403,6 +403,19 @@ export class BookService {
);
}
updateBookReadStatus(id: number, status: ReadStatus): Observable<void> {
return this.http.put<void>(`${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<any>(`${this.url}/${bookId}/metadata/restore`);
}
updateBookReadStatus(id: number, status: ReadStatus): Observable<void> {
return this.http.put<void>(`${this.url}/${id}/read-status`, {status});
}
}