diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 92400bc..f96d3a5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -92,13 +92,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo } if (preferencesData.status === "fulfilled") { - const { showThumbnails, cacheMode, showOnlyUnread, debug } = preferencesData.value; - preferences = { - showThumbnails, - cacheMode, - showOnlyUnread, - debug, - }; + preferences = preferencesData.value; } } catch (error) { console.error("Erreur lors du chargement des données de la sidebar:", error); diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index 8f538f2..8ee020d 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -13,19 +13,24 @@ import { AppError } from "@/utils/errors"; interface PageProps { params: { seriesId: string }; - searchParams: { page?: string; unread?: string }; + searchParams: { page?: string; unread?: string; size?: string }; } -const PAGE_SIZE = 24; +const DEFAULT_PAGE_SIZE = 20; -async function getSeriesBooks(seriesId: string, page: number = 1, unreadOnly: boolean = false) { +async function getSeriesBooks( + seriesId: string, + page: number = 1, + unreadOnly: boolean = false, + size: number = DEFAULT_PAGE_SIZE +) { try { const pageIndex = page - 1; const books: LibraryResponse = await SeriesService.getSeriesBooks( seriesId, pageIndex, - PAGE_SIZE, + size, unreadOnly ); const series: KomgaSeries = await SeriesService.getSeries(seriesId); @@ -54,8 +59,10 @@ async function SeriesPage({ params, searchParams }: PageProps) { const seriesId = (await params).seriesId; const page = (await searchParams).page; const unread = (await searchParams).unread; + const size = (await searchParams).size; const currentPage = page ? parseInt(page) : 1; + const pageSize = size ? parseInt(size) : DEFAULT_PAGE_SIZE; const preferences: UserPreferences = await PreferencesService.getPreferences(); // Utiliser le paramètre d'URL s'il existe, sinon utiliser la préférence utilisateur @@ -63,7 +70,7 @@ async function SeriesPage({ params, searchParams }: PageProps) { try { const { data: books, series }: { data: LibraryResponse; series: KomgaSeries } = - await getSeriesBooks(seriesId, currentPage, unreadOnly); + await getSeriesBooks(seriesId, currentPage, unreadOnly, pageSize); return (
@@ -73,7 +80,6 @@ async function SeriesPage({ params, searchParams }: PageProps) { currentPage={currentPage} totalPages={books.totalPages} totalElements={books.totalElements} - pageSize={PAGE_SIZE} defaultShowOnlyUnread={preferences.showOnlyUnread} showOnlyUnread={unreadOnly} /> diff --git a/src/components/common/CompactModeButton.tsx b/src/components/common/CompactModeButton.tsx new file mode 100644 index 0000000..2068454 --- /dev/null +++ b/src/components/common/CompactModeButton.tsx @@ -0,0 +1,37 @@ +import { useDisplayPreferences } from "@/hooks/useDisplayPreferences"; +import { useTranslate } from "@/hooks/useTranslate"; +import { LayoutGrid, LayoutTemplate } from "lucide-react"; + +interface CompactModeButtonProps { + onToggle?: (isCompact: boolean) => void; +} + +export function CompactModeButton({ onToggle }: CompactModeButtonProps) { + const { isCompact, handleCompactToggle } = useDisplayPreferences(); + const { t } = useTranslate(); + + const handleClick = async () => { + const newCompactState = !isCompact; + await handleCompactToggle(newCompactState); + onToggle?.(newCompactState); + }; + + return ( + + ); +} diff --git a/src/components/common/PageSizeSelect.tsx b/src/components/common/PageSizeSelect.tsx new file mode 100644 index 0000000..e45fe93 --- /dev/null +++ b/src/components/common/PageSizeSelect.tsx @@ -0,0 +1,37 @@ +import { useDisplayPreferences } from "@/hooks/useDisplayPreferences"; +import { LayoutList } from "lucide-react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +interface PageSizeSelectProps { + onSizeChange?: (size: number) => void; +} + +export function PageSizeSelect({ onSizeChange }: PageSizeSelectProps) { + const { itemsPerPage, handlePageSizeChange } = useDisplayPreferences(); + + const handleChange = async (value: string) => { + const size = parseInt(value); + await handlePageSizeChange(size); + onSizeChange?.(size); + }; + + return ( + + ); +} diff --git a/src/components/common/UnreadFilterButton.tsx b/src/components/common/UnreadFilterButton.tsx new file mode 100644 index 0000000..0e2e7d6 --- /dev/null +++ b/src/components/common/UnreadFilterButton.tsx @@ -0,0 +1,21 @@ +import { useTranslate } from "@/hooks/useTranslate"; +import { Filter } from "lucide-react"; + +interface UnreadFilterButtonProps { + showOnlyUnread: boolean; + onToggle: () => void; +} + +export function UnreadFilterButton({ showOnlyUnread, onToggle }: UnreadFilterButtonProps) { + const { t } = useTranslate(); + + return ( + + ); +} diff --git a/src/components/library/PaginatedSeriesGrid.tsx b/src/components/library/PaginatedSeriesGrid.tsx index 945115d..206ce4e 100644 --- a/src/components/library/PaginatedSeriesGrid.tsx +++ b/src/components/library/PaginatedSeriesGrid.tsx @@ -4,19 +4,15 @@ import { SeriesGrid } from "./SeriesGrid"; import { Pagination } from "@/components/ui/Pagination"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { useState, useEffect } from "react"; -import { Loader2, Filter, LayoutGrid, LayoutList, LayoutTemplate } from "lucide-react"; +import { Loader2 } from "lucide-react"; import { cn } from "@/lib/utils"; import type { KomgaSeries } from "@/types/komga"; import { SearchInput } from "./SearchInput"; import { useTranslate } from "@/hooks/useTranslate"; import { useDisplayPreferences } from "@/hooks/useDisplayPreferences"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { PageSizeSelect } from "@/components/common/PageSizeSelect"; +import { CompactModeButton } from "@/components/common/CompactModeButton"; +import { UnreadFilterButton } from "@/components/common/UnreadFilterButton"; interface PaginatedSeriesGridProps { series: KomgaSeries[]; @@ -40,15 +36,13 @@ export function PaginatedSeriesGrid({ const searchParams = useSearchParams(); const [isChangingPage, setIsChangingPage] = useState(false); const [showOnlyUnread, setShowOnlyUnread] = useState(initialShowOnlyUnread); - const { isCompact, itemsPerPage, handleCompactToggle, handlePageSizeChange } = - useDisplayPreferences(); + const { isCompact, itemsPerPage } = useDisplayPreferences(); const { t } = useTranslate(); const updateUrlParams = async (updates: Record) => { setIsChangingPage(true); const params = new URLSearchParams(searchParams.toString()); - // Mettre à jour les paramètres Object.entries(updates).forEach(([key, value]) => { if (value === null) { params.delete(key); @@ -90,21 +84,17 @@ export function PaginatedSeriesGrid({ }); }; - const handleCompactToggleClick = async () => { - const newCompactState = !isCompact; - await handleCompactToggle(newCompactState); + const handleCompactToggle = async (newCompactState: boolean) => { await updateUrlParams({ page: "1", compact: newCompactState.toString(), }); }; - const handlePageSizeChangeClick = async (value: string) => { - const size = parseInt(value); - await handlePageSizeChange(size); + const handlePageSizeChange = async (size: number) => { await updateUrlParams({ page: "1", - size: value, + size: size.toString(), }); }; @@ -124,46 +114,17 @@ export function PaginatedSeriesGrid({ return (
-
-
- -
-
-

{getShowingText()}

- - - +
+

{getShowingText()}

+
+
+ +
+
+ + + +
diff --git a/src/components/library/SearchInput.tsx b/src/components/library/SearchInput.tsx index a685b25..8d5081f 100644 --- a/src/components/library/SearchInput.tsx +++ b/src/components/library/SearchInput.tsx @@ -34,7 +34,7 @@ export const SearchInput = ({ placeholder }: SearchInputProps) => { }, 300); return ( -
+
void; + isCompact?: boolean; } -export function BookGrid({ books, onBookClick }: BookGridProps) { +export function BookGrid({ books, onBookClick, isCompact = false }: BookGridProps) { const [localBooks, setLocalBooks] = useState(books); const { t } = useTranslate(); @@ -21,10 +23,11 @@ export function BookGrid({ books, onBookClick }: BookGridProps) { if (!localBooks.length) { return (
-

{t("books.empty")}

+

{t("books.empty")}

); } + const handleOnSuccess = (book: KomgaBook, action: "read" | "unread") => { if (action === "read") { setLocalBooks( @@ -58,12 +61,22 @@ export function BookGrid({ books, onBookClick }: BookGridProps) { }; return ( -
+
{localBooks.map((book) => { return (
onBookClick(book)} diff --git a/src/components/series/PaginatedBookGrid.tsx b/src/components/series/PaginatedBookGrid.tsx index 636270d..e965abb 100644 --- a/src/components/series/PaginatedBookGrid.tsx +++ b/src/components/series/PaginatedBookGrid.tsx @@ -4,17 +4,20 @@ import { BookGrid } from "./BookGrid"; import { Pagination } from "@/components/ui/Pagination"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { useState, useEffect } from "react"; -import { Loader2, Filter } from "lucide-react"; +import { Loader2 } from "lucide-react"; import { cn } from "@/lib/utils"; import type { KomgaBook } from "@/types/komga"; import { useTranslate } from "@/hooks/useTranslate"; +import { useDisplayPreferences } from "@/hooks/useDisplayPreferences"; +import { PageSizeSelect } from "@/components/common/PageSizeSelect"; +import { CompactModeButton } from "@/components/common/CompactModeButton"; +import { UnreadFilterButton } from "@/components/common/UnreadFilterButton"; interface PaginatedBookGridProps { books: KomgaBook[]; currentPage: number; totalPages: number; totalElements: number; - pageSize: number; defaultShowOnlyUnread: boolean; showOnlyUnread: boolean; } @@ -24,7 +27,6 @@ export function PaginatedBookGrid({ currentPage, totalPages, totalElements, - pageSize, defaultShowOnlyUnread, showOnlyUnread: initialShowOnlyUnread, }: PaginatedBookGridProps) { @@ -33,55 +35,75 @@ export function PaginatedBookGrid({ const searchParams = useSearchParams(); const [isChangingPage, setIsChangingPage] = useState(false); const [showOnlyUnread, setShowOnlyUnread] = useState(initialShowOnlyUnread); + const { isCompact, itemsPerPage } = useDisplayPreferences(); const { t } = useTranslate(); - // Réinitialiser l'état de chargement quand les tomes changent + const updateUrlParams = async (updates: Record) => { + setIsChangingPage(true); + const params = new URLSearchParams(searchParams.toString()); + + Object.entries(updates).forEach(([key, value]) => { + if (value === null) { + params.delete(key); + } else { + params.set(key, value); + } + }); + + await router.push(`${pathname}?${params.toString()}`); + }; + + // Reset loading state when books change useEffect(() => { setIsChangingPage(false); }, [books]); - // Mettre à jour l'état local quand la prop change + // Update local state when prop changes useEffect(() => { setShowOnlyUnread(initialShowOnlyUnread); }, [initialShowOnlyUnread]); - // Appliquer le filtre par défaut au chargement initial + // Apply default filter on initial load useEffect(() => { if (defaultShowOnlyUnread && !searchParams.has("unread")) { - const params = new URLSearchParams(searchParams.toString()); - params.set("page", "1"); - params.set("unread", "true"); - router.push(`${pathname}?${params.toString()}`); + updateUrlParams({ page: "1", unread: "true" }); } }, [defaultShowOnlyUnread, pathname, router, searchParams]); const handlePageChange = async (page: number) => { - setIsChangingPage(true); - const params = new URLSearchParams(searchParams.toString()); - params.set("page", page.toString()); - params.set("unread", showOnlyUnread.toString()); - await router.push(`${pathname}?${params.toString()}`); + await updateUrlParams({ page: page.toString() }); }; const handleUnreadFilter = async () => { - setIsChangingPage(true); - const params = new URLSearchParams(searchParams.toString()); - params.set("page", "1"); - const newUnreadState = !showOnlyUnread; setShowOnlyUnread(newUnreadState); - params.set("unread", newUnreadState.toString()); + await updateUrlParams({ + page: "1", + unread: newUnreadState ? "true" : "false", + }); + }; - await router.push(`${pathname}?${params.toString()}`); + const handleCompactToggle = async (newCompactState: boolean) => { + await updateUrlParams({ + page: "1", + compact: newCompactState.toString(), + }); + }; + + const handlePageSizeChange = async (size: number) => { + await updateUrlParams({ + page: "1", + size: size.toString(), + }); }; const handleBookClick = (book: KomgaBook) => { router.push(`/books/${book.id}`); }; - // Calcul des indices de début et de fin pour l'affichage - const startIndex = (currentPage - 1) * pageSize + 1; - const endIndex = Math.min(currentPage * pageSize, totalElements); + // Calculate start and end indices for display + const startIndex = (currentPage - 1) * itemsPerPage + 1; + const endIndex = Math.min(currentPage * itemsPerPage, totalElements); const getShowingText = () => { if (!totalElements) return t("books.empty"); @@ -95,19 +117,17 @@ export function PaginatedBookGrid({ return (
-
-

{getShowingText()}

- +
+

{getShowingText()}

+
+ + + +
- {/* Indicateur de chargement */} + {/* Loading indicator */} {isChangingPage && (
@@ -117,14 +137,14 @@ export function PaginatedBookGrid({
)} - {/* Grille avec animation de transition */} + {/* Grid with transition animation */}
- +
diff --git a/src/contexts/PreferencesContext.tsx b/src/contexts/PreferencesContext.tsx index e919bcb..65f7c07 100644 --- a/src/contexts/PreferencesContext.tsx +++ b/src/contexts/PreferencesContext.tsx @@ -46,7 +46,6 @@ export function PreferencesProvider({ }; const updatePreferences = async (newPreferences: Partial) => { - try { const response = await fetch("/api/preferences", { method: "PUT", diff --git a/src/hooks/useDisplayPreferences.ts b/src/hooks/useDisplayPreferences.ts index 1b1fe17..ec648a7 100644 --- a/src/hooks/useDisplayPreferences.ts +++ b/src/hooks/useDisplayPreferences.ts @@ -1,11 +1,7 @@ import { usePreferences } from "@/contexts/PreferencesContext"; -import { useToast } from "@/components/ui/use-toast"; -import { useTranslate } from "@/hooks/useTranslate"; export function useDisplayPreferences() { const { preferences, updatePreferences } = usePreferences(); - const { toast } = useToast(); - const { t } = useTranslate(); const handleCompactToggle = async (checked: boolean) => { try { @@ -15,17 +11,8 @@ export function useDisplayPreferences() { compact: checked, }, }); - toast({ - title: t("settings.title"), - description: t("settings.komga.messages.configSaved"), - }); } catch (error) { console.error("Erreur lors de la mise à jour du mode compact:", error); - toast({ - variant: "destructive", - title: t("settings.error.title"), - description: t("settings.error.message"), - }); } }; @@ -37,17 +24,8 @@ export function useDisplayPreferences() { itemsPerPage: size, }, }); - toast({ - title: t("settings.title"), - description: t("settings.komga.messages.configSaved"), - }); } catch (error) { console.error("Erreur lors de la mise à jour de la taille de page:", error); - toast({ - variant: "destructive", - title: t("settings.error.title"), - description: t("settings.error.message"), - }); } };