feat: display missing books count badge on series covers

Show an orange badge with BookX icon on series covers when the Stripstream
API reports missing books in the collection. Also display a warning status
badge on the series detail page header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 13:17:53 +01:00
parent f48d894eca
commit a82ce024ee
7 changed files with 20 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { Book, BookOpen, BookMarked, Star, StarOff, User } from "lucide-react"; import { Book, BookOpen, BookMarked, BookX, Star, StarOff, User } from "lucide-react";
import type { NormalizedSeries } from "@/lib/providers/types"; import type { NormalizedSeries } from "@/lib/providers/types";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
@@ -160,6 +160,11 @@ export const SeriesHeader = ({ series, refreshSeries, initialIsFavorite }: Serie
? t("series.header.books", { count: series.bookCount }) ? t("series.header.books", { count: series.bookCount })
: t("series.header.books_plural", { count: series.bookCount })} : t("series.header.books_plural", { count: series.bookCount })}
</span> </span>
{series.missingCount != null && series.missingCount > 0 && (
<StatusBadge status="warning" icon={BookX}>
{t("series.header.missing", { count: series.missingCount })}
</StatusBadge>
)}
<IconButton <IconButton
variant="ghost" variant="ghost"
size="icon" size="icon"

View File

@@ -1,4 +1,5 @@
import { ProgressBar } from "./progress-bar"; import { ProgressBar } from "./progress-bar";
import { BookX } from "lucide-react";
import type { SeriesCoverProps } from "./cover-utils"; import type { SeriesCoverProps } from "./cover-utils";
export function SeriesCover({ export function SeriesCover({
@@ -12,6 +13,7 @@ export function SeriesCover({
const readBooks = series.booksReadCount; const readBooks = series.booksReadCount;
const totalBooks = series.bookCount; const totalBooks = series.bookCount;
const showProgress = Boolean(showProgressUi && totalBooks > 0 && readBooks > 0 && !isCompleted); const showProgress = Boolean(showProgressUi && totalBooks > 0 && readBooks > 0 && !isCompleted);
const missingCount = series.missingCount;
return ( return (
<div className="relative w-full h-full"> <div className="relative w-full h-full">
@@ -27,6 +29,12 @@ export function SeriesCover({
.filter(Boolean) .filter(Boolean)
.join(" ")} .join(" ")}
/> />
{showProgressUi && missingCount != null && missingCount > 0 && (
<div className="absolute top-1.5 right-1.5 flex items-center gap-0.5 rounded-full bg-orange-500/90 px-1.5 py-0.5 text-white shadow-md backdrop-blur-sm">
<BookX className="h-3 w-3" />
<span className="text-[10px] font-bold leading-none">{missingCount}</span>
</div>
)}
{showProgress ? <ProgressBar progress={readBooks} total={totalBooks} type="series" /> : null} {showProgress ? <ProgressBar progress={readBooks} total={totalBooks} type="series" /> : null}
</div> </div>
); );

View File

@@ -263,6 +263,7 @@
}, },
"showMore": "Show more", "showMore": "Show more",
"showLess": "Show less", "showLess": "Show less",
"missing": "{{count}} missing",
"toggleSidebar": "Toggle sidebar", "toggleSidebar": "Toggle sidebar",
"toggleTheme": "Toggle theme" "toggleTheme": "Toggle theme"
} }

View File

@@ -262,7 +262,8 @@
"remove": "Retiré des favoris" "remove": "Retiré des favoris"
}, },
"showMore": "Voir plus", "showMore": "Voir plus",
"showLess": "Voir moins" "showLess": "Voir moins",
"missing": "{{count}} manquant(s)"
} }
}, },
"books": { "books": {

View File

@@ -79,6 +79,7 @@ export class StripstreamAdapter {
genres: [], genres: [],
tags: [], tags: [],
createdAt: null, createdAt: null,
missingCount: series.missing_count ?? null,
}; };
} }

View File

@@ -25,6 +25,7 @@ export interface NormalizedSeries {
genres?: string[]; genres?: string[];
tags?: string[]; tags?: string[];
createdAt?: string | null; createdAt?: string | null;
missingCount?: number | null;
} }
export interface NormalizedBook { export interface NormalizedBook {

View File

@@ -49,6 +49,7 @@ export interface StripstreamSeriesItem {
books_read_count: number; books_read_count: number;
first_book_id: string; first_book_id: string;
library_id: string; library_id: string;
missing_count?: number | null;
} }
export interface StripstreamSeriesPage { export interface StripstreamSeriesPage {