Files
Mangarr/assets/react/app/presentation/components/SearchBar/SearchBar.jsx
2025-02-17 12:02:56 +01:00

125 lines
4.4 KiB
JavaScript

import React, { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch, faPlus } from '@fortawesome/free-solid-svg-icons';
import { ApiMangaRepository } from '../../../infrastructure/api/apiMangaRepository.js';
import { SearchMangas } from '../../../application/useCases/searchMangas.js';
const mangaRepository = new ApiMangaRepository();
const searchMangas = new SearchMangas(mangaRepository);
export function SearchBar({ onMangaClick, onAddMangaClick }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [hasSearched, setHasSearched] = useState(false);
const searchRef = useRef(null);
useEffect(() => {
const handleClickOutside = (event) => {
if (searchRef.current && !searchRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
useEffect(() => {
const searchManga = async () => {
if (!query.trim()) {
setResults([]);
setHasSearched(false);
return;
}
setLoading(true);
try {
const searchResults = await searchMangas.execute(query);
setResults(searchResults);
setHasSearched(true);
} catch (error) {
console.error('Search error:', error);
} finally {
setLoading(false);
}
};
const timeoutId = setTimeout(searchManga, 300);
return () => clearTimeout(timeoutId);
}, [query]);
return (
<div ref={searchRef} className="relative flex-1 max-w-xl mx-4">
<div className="flex items-center py-1">
<FontAwesomeIcon
icon={faSearch}
className="text-white"
/>
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value);
setIsOpen(true);
}}
onFocus={() => setIsOpen(true)}
placeholder="Rechercher"
className="appearance-none outline-none ml-2 pl-0 bg-transparent border-b border-white w-full placeholder:text-white text-white py-1 px-2 leading-tight transition-all duration-500 ease-in-out focus:placeholder:text-opacity-0 focus:border-opacity-0"
/>
</div>
{isOpen && query.trim() && (
<div className="absolute w-full mt-2 bg-gray-800/95 backdrop-blur-sm rounded-lg shadow-lg border border-gray-700 max-h-96 overflow-y-auto z-50">
{loading ? (
<div className="p-4 text-center text-gray-400">Chargement...</div>
) : results.length > 0 ? (
<div className="py-2">
<h3 className="px-4 py-2 text-sm font-semibold text-gray-400">
Mangas existants
</h3>
{results.map((manga) => (
<button
key={manga.id}
onClick={() => {
onMangaClick(manga.slug);
setIsOpen(false);
setQuery('');
setHasSearched(false);
}}
className="w-full px-4 py-2 flex items-center gap-3 hover:bg-gray-700/50 text-white"
>
<img
src={manga.imageUrl}
alt={manga.title}
className="w-10 h-14 object-cover rounded"
/>
<div className="text-left">
<div className="font-medium">{manga.title}</div>
<div className="text-sm text-gray-400">{manga.author} ({manga.publicationYear})</div>
</div>
</button>
))}
</div>
) : hasSearched && (
<div className="py-2">
<button
onClick={() => {
onAddMangaClick(query);
setIsOpen(false);
setQuery('');
setHasSearched(false);
}}
className="w-full px-4 py-2 flex items-center gap-2 text-green-400 hover:bg-gray-700/50"
>
<FontAwesomeIcon icon={faPlus} />
<span>Ajouter "{query}"</span>
</button>
</div>
)}
</div>
)}
</div>
);
}