Files
labiblioteca/src/components/BookList.js
2025-11-18 21:45:12 +01:00

179 líneas
6.1 KiB
JavaScript

import React, { useState } from 'react';
import { FaBook, FaSearch, FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import '../styles/BookList.css';
/**
* Componente para mostrar y buscar libros en el catálogo
*/
const BookList = ({ books, onBookSelect, isLoading }) => {
const [searchTerm, setSearchTerm] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
// Filtrar libros según el término de búsqueda
const filteredBooks = books.filter((book) =>
book.title.toLowerCase().includes(searchTerm.toLowerCase())
);
// Calcular paginación
const totalPages = Math.ceil(filteredBooks.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentBooks = filteredBooks.slice(startIndex, endIndex);
// Resetear a la primera página cuando cambia la búsqueda o items por página
const handleSearchChange = (value) => {
setSearchTerm(value);
setCurrentPage(1);
};
const handleItemsPerPageChange = (value) => {
setItemsPerPage(Number(value));
setCurrentPage(1);
};
const goToPage = (page) => {
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
};
if (isLoading) {
return (
<div className="book-list-container">
<div className="loading">
<div className="spinner"></div>
<p>Cargando catálogo desde IPFS...</p>
</div>
</div>
);
}
return (
<div className="book-list-container">
<div className="search-box" role="search">
<label htmlFor="search-input" className="visually-hidden">Buscar libros por título</label>
<FaSearch className="search-icon" aria-hidden="true" />
<input
id="search-input"
type="search"
placeholder="Buscar libros por título..."
value={searchTerm}
onChange={(e) => handleSearchChange(e.target.value)}
className="search-input"
aria-label="Buscar libros"
aria-describedby="search-results-count"
aria-controls="book-list"
/>
{searchTerm && (
<button
className="clear-search"
onClick={() => handleSearchChange('')}
aria-label="Limpiar búsqueda"
>
</button>
)}
</div>
<div className="controls-bar">
<div className="book-count" id="search-results-count" role="status" aria-live="polite" aria-atomic="true">
{filteredBooks.length} {filteredBooks.length === 1 ? 'libro encontrado' : 'libros encontrados'}
</div>
<div className="items-per-page">
<label htmlFor="items-per-page">Mostrar:</label>
<select
id="items-per-page"
value={itemsPerPage}
onChange={(e) => handleItemsPerPageChange(e.target.value)}
className="items-select"
aria-label="Libros por página"
aria-describedby="search-results-count"
aria-controls="book-list"
>
<option value={10}>10</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
<span>por página</span>
</div>
</div>
<ul className="book-list" id="book-list" role="list" aria-label="Lista de libros">
{filteredBooks.length === 0 ? (
<li className="no-results" role="status">
<FaBook className="no-results-icon" aria-hidden="true" />
<p>No se encontraron libros</p>
<small>Intenta con otros términos de búsqueda</small>
</li>
) : (
currentBooks.map((book, index) => (
<li key={book.id} className="book-list-item" role="listitem">
<a
href={`?epub=${book.ipfsHash}&gateway=${encodeURIComponent(window.location.origin)}`}
className="book-item"
onClick={(e) => {
e.preventDefault();
onBookSelect(book);
}}
aria-label={`Abrir libro: ${book.title}`}
>
<FaBook className="book-icon" aria-hidden="true" />
<div className="book-info">
<h3 className="book-title">{book.title}</h3>
<p className="book-filename" aria-label={`Archivo: ${book.filename}`}>
<small>{book.filename}</small>
</p>
</div>
</a>
</li>
))
)}
</ul>
{totalPages > 1 && (
<nav className="pagination" role="navigation" aria-label="Paginación de libros">
<button
className="pagination-button"
onClick={() => goToPage(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Ir a página anterior"
aria-disabled={currentPage === 1}
>
<FaChevronLeft aria-hidden="true" />
<span className="visually-hidden">Anterior</span>
</button>
<div className="pagination-info">
<label htmlFor="page-input" className="visually-hidden">Ir a página número</label>
<span aria-hidden="true">Página </span>
<input
id="page-input"
type="number"
min="1"
max={totalPages}
value={currentPage}
onChange={(e) => goToPage(Number(e.target.value))}
className="page-input"
aria-label={`Página actual ${currentPage} de ${totalPages}`}
/>
<span aria-hidden="true"> de {totalPages}</span>
</div>
<button
className="pagination-button"
onClick={() => goToPage(currentPage + 1)}
disabled={currentPage === totalPages}
aria-label="Ir a página siguiente"
aria-disabled={currentPage === totalPages}
>
<FaChevronRight aria-hidden="true" />
<span className="visually-hidden">Siguiente</span>
</button>
</nav>
)}
</div>
);
};
export default BookList;