diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index 3731c57..df7fac2 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -6,7 +6,7 @@ import { RefreshButton } from "@/components/library/RefreshButton"; interface PageProps { params: { libraryId: string }; - searchParams: { page?: string; unread?: string }; + searchParams: { page?: string; unread?: string; search?: string }; } const PAGE_SIZE = 20; @@ -25,7 +25,12 @@ async function refreshLibrary(libraryId: string) { } } -async function getLibrarySeries(libraryId: string, page: number = 1, unreadOnly: boolean = false) { +async function getLibrarySeries( + libraryId: string, + page: number = 1, + unreadOnly: boolean = false, + search?: string +) { try { const pageIndex = page - 1; @@ -33,7 +38,8 @@ async function getLibrarySeries(libraryId: string, page: number = 1, unreadOnly: libraryId, pageIndex, PAGE_SIZE, - unreadOnly + unreadOnly, + search ); const library = await LibraryService.getLibrary(libraryId); @@ -55,7 +61,8 @@ export default async function LibraryPage({ params, searchParams }: PageProps) { const { data: series, library } = await getLibrarySeries( params.libraryId, currentPage, - unreadOnly + unreadOnly, + searchParams.search ); return ( diff --git a/src/components/library/PaginatedSeriesGrid.tsx b/src/components/library/PaginatedSeriesGrid.tsx index 77e6c6f..ea68c4d 100644 --- a/src/components/library/PaginatedSeriesGrid.tsx +++ b/src/components/library/PaginatedSeriesGrid.tsx @@ -7,6 +7,7 @@ import { useState, useEffect } from "react"; import { Loader2, Filter } from "lucide-react"; import { cn } from "@/lib/utils"; import { KomgaSeries } from "@/types/komga"; +import { SearchInput } from "./SearchInput"; interface PaginatedSeriesGridProps { series: KomgaSeries[]; @@ -87,24 +88,29 @@ export function PaginatedSeriesGrid({ return (
-

- {totalElements > 0 ? ( - <> - Affichage des séries {startIndex} à{" "} - {endIndex} sur{" "} - {totalElements} - - ) : ( - "Aucune série trouvée" - )} -

- +
+ +
+
+

+ {totalElements > 0 ? ( + <> + Affichage des séries {startIndex} à{" "} + {endIndex} sur{" "} + {totalElements} + + ) : ( + "Aucune série trouvée" + )} +

+ +
diff --git a/src/components/library/SearchInput.tsx b/src/components/library/SearchInput.tsx new file mode 100644 index 0000000..7f08b26 --- /dev/null +++ b/src/components/library/SearchInput.tsx @@ -0,0 +1,54 @@ +import { Search } from "lucide-react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useTransition } from "react"; +import { Input } from "@/components/ui/input"; +import { debounce } from "@/lib/utils"; + +interface SearchInputProps { + placeholder?: string; +} + +export const SearchInput = ({ placeholder = "Rechercher une série..." }: SearchInputProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [isPending, startTransition] = useTransition(); + + const createQueryString = useCallback( + (name: string, value: string) => { + const params = new URLSearchParams(searchParams.toString()); + if (value) { + params.set(name, value); + } else { + params.delete(name); + } + return params.toString(); + }, + [searchParams] + ); + + const handleSearch = debounce((term: string) => { + startTransition(() => { + const query = createQueryString("search", term); + router.push(`?${query}`); + }); + }, 300); + + return ( +
+ + handleSearch(e.target.value)} + aria-label="Rechercher une série" + /> + {isPending && ( +
+
+
+ )} +
+ ); +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..a4f7d29 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export interface InputProps extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/lib/services/library.service.ts b/src/lib/services/library.service.ts index f322883..94b25b6 100644 --- a/src/lib/services/library.service.ts +++ b/src/lib/services/library.service.ts @@ -33,7 +33,8 @@ export class LibraryService extends BaseApiService { libraryId: string, page: number = 0, size: number = 20, - unreadOnly: boolean = false + unreadOnly: boolean = false, + search?: string ): Promise> { try { const config = await this.getKomgaConfig(); @@ -42,11 +43,12 @@ export class LibraryService extends BaseApiService { page: page.toString(), size: size.toString(), ...(unreadOnly && { read_status: "UNREAD,IN_PROGRESS" }), + ...(search && { search }), }); const headers = this.getAuthHeaders(config); return this.fetchWithCache>( - `library-${libraryId}-series-${page}-${size}-${unreadOnly}`, + `library-${libraryId}-series-${page}-${size}-${unreadOnly}-${search}`, async () => this.fetchFromApi>(url, headers), "SERIES" ); @@ -56,6 +58,6 @@ export class LibraryService extends BaseApiService { } static async clearLibrarySeriesCache(libraryId: string) { - serverCacheService.delete(`library-${libraryId}-series`); + serverCacheService.deleteAll(`library-${libraryId}-series`); } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 5ecd27d..2fa517c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -13,3 +13,20 @@ export function formatDate(date: string | Date): string { year: "numeric", }); } + +export function debounce void>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout; + + return function executedFunction(...args: Parameters) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +}