feat(favorites): add in sidebar

This commit is contained in:
Julien Froidefond
2025-02-13 22:35:59 +01:00
parent 65e62f5800
commit 6a530afac1
3 changed files with 82 additions and 22 deletions

View File

@@ -9,8 +9,9 @@ Application web moderne pour la lecture de BD/mangas/comics via un serveur Komga
### 📚 Gestion des séries
- [x] Système de favoris
- [x] Ajout/suppression des favoris (stockage local)
- [ ] Menu dédié dans la sidebar
- [x] Service de gestion des favoris
- [x] Bouton d'ajout/retrait des favoris
- [x] Menu dédié dans la sidebar avec la liste des séries favorites
- [ ] Carousel dédié dans sur la homepage de toutes les séries favorites
- [ ] Vue liste/grille configurable
- [ ] Filtres et tri avancés

View File

@@ -1,19 +1,12 @@
import { NextResponse } from "next/server";
import { SeriesService } from "@/lib/services/series.service";
export const dynamic = "force-dynamic";
export async function GET(request: Request, { params }: { params: { seriesId: string } }) {
try {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") || "0");
const size = parseInt(searchParams.get("size") || "24");
const unreadOnly = searchParams.get("unread") === "true";
const [series, books] = await Promise.all([
SeriesService.getSeries(params.seriesId),
SeriesService.getSeriesBooks(params.seriesId, page, size, unreadOnly),
]);
return NextResponse.json({ series, books });
const series = await SeriesService.getSeries(params.seriesId);
return NextResponse.json(series);
} catch (error) {
console.error("API Series - Erreur:", error);
return NextResponse.json(

View File

@@ -1,11 +1,12 @@
import { BookOpen, Home, Library, Settings, LogOut, RefreshCw } from "lucide-react";
import { BookOpen, Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { authService } from "@/lib/services/auth.service";
import { useEffect, useState, useCallback } from "react";
import { KomgaLibrary } from "@/types/komga";
import { KomgaLibrary, KomgaSeries } from "@/types/komga";
import { storageService } from "@/lib/services/storage.service";
import { FavoriteService } from "@/lib/services/favorite.service";
interface SidebarProps {
isOpen: boolean;
@@ -15,21 +16,19 @@ export function Sidebar({ isOpen }: SidebarProps) {
const pathname = usePathname();
const router = useRouter();
const [libraries, setLibraries] = useState<KomgaLibrary[]>([]);
const [favorites, setFavorites] = useState<KomgaSeries[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false);
// Initialiser l'authentification au montage du composant
const [isLoadingFavorites, setIsLoadingFavorites] = useState(true);
const fetchLibraries = useCallback(async () => {
setIsLoading(true);
console.log("Sidebar - Fetching libraries...");
try {
const response = await fetch("/api/komga/libraries");
if (!response.ok) {
throw new Error("Erreur lors de la récupération des bibliothèques");
}
const data = await response.json();
console.log("Sidebar - Libraries fetched:", data.length);
setLibraries(data);
} catch (error) {
console.error("Erreur:", error);
@@ -40,20 +39,59 @@ export function Sidebar({ isOpen }: SidebarProps) {
}
}, []);
const fetchFavorites = useCallback(async () => {
setIsLoadingFavorites(true);
try {
const favoriteIds = FavoriteService.getAllFavoriteIds();
if (favoriteIds.length === 0) {
setFavorites([]);
return;
}
const promises = favoriteIds.map(async (id) => {
const response = await fetch(`/api/komga/series/${id}`);
if (!response.ok) return null;
return response.json();
});
const results = await Promise.all(promises);
setFavorites(results.filter((series): series is KomgaSeries => series !== null));
} catch (error) {
console.error("Erreur lors de la récupération des favoris:", error);
setFavorites([]);
} finally {
setIsLoadingFavorites(false);
}
}, []);
// Chargement initial des données
useEffect(() => {
fetchLibraries();
}, [pathname, fetchLibraries]);
fetchFavorites();
}, []); // Suppression de la dépendance pathname
// Mettre à jour les favoris quand ils changent dans le localStorage
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === "stripstream_favorites") {
fetchFavorites();
}
};
window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, [fetchFavorites]);
const handleRefresh = async () => {
setIsRefreshing(true);
await fetchLibraries();
await Promise.all([fetchLibraries(), fetchFavorites()]);
};
const handleLogout = () => {
console.log("Sidebar - Logging out");
authService.logout();
storageService.clearAll();
setLibraries([]);
setFavorites([]);
router.push("/login");
};
@@ -95,6 +133,34 @@ export function Sidebar({ isOpen }: SidebarProps) {
</div>
</div>
<div className="px-3 py-2">
<div className="space-y-1">
<div className="mb-2 px-4 flex items-center justify-between">
<h2 className="text-lg font-semibold tracking-tight">Favoris</h2>
<span className="text-xs text-muted-foreground">{favorites.length}</span>
</div>
{isLoadingFavorites ? (
<div className="px-3 py-2 text-sm text-muted-foreground">Chargement...</div>
) : favorites.length === 0 ? (
<div className="px-3 py-2 text-sm text-muted-foreground">Aucun favori</div>
) : (
favorites.map((series) => (
<Link
key={series.id}
href={`/series/${series.id}`}
className={cn(
"flex items-center rounded-lg px-3 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground",
pathname === `/series/${series.id}` ? "bg-accent" : "transparent"
)}
>
<Star className="mr-2 h-4 w-4 fill-yellow-400 text-yellow-400" />
<span className="truncate">{series.metadata.title}</span>
</Link>
))
)}
</div>
</div>
<div className="px-3 py-2">
<div className="space-y-1">
<div className="mb-2 px-4 flex items-center justify-between">