2026-03-22 18:45:37 +01:00
|
|
|
import { Link } from 'react-router-dom'
|
|
|
|
|
|
|
|
|
|
const TABS = [
|
|
|
|
|
['all', 'Tous'],
|
|
|
|
|
['read', 'Lus'],
|
|
|
|
|
['unread', 'À lire'],
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
export function BookList({
|
|
|
|
|
books,
|
|
|
|
|
filter,
|
|
|
|
|
query,
|
|
|
|
|
onFilterChange,
|
|
|
|
|
onQueryChange,
|
|
|
|
|
onToggleRead,
|
|
|
|
|
onRemove,
|
|
|
|
|
}) {
|
|
|
|
|
const q = query.trim().toLowerCase()
|
|
|
|
|
const filtered = books.filter((b) => {
|
|
|
|
|
if (filter === 'read' && !b.read) return false
|
|
|
|
|
if (filter === 'unread' && b.read) return false
|
|
|
|
|
if (!q) return true
|
|
|
|
|
return (
|
|
|
|
|
b.title.toLowerCase().includes(q) ||
|
|
|
|
|
b.author.toLowerCase().includes(q) ||
|
|
|
|
|
(b.genre && b.genre.toLowerCase().includes(q))
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<section className="book-list-section">
|
|
|
|
|
<div className="toolbar">
|
|
|
|
|
<input
|
|
|
|
|
type="search"
|
|
|
|
|
className="search"
|
|
|
|
|
placeholder="Rechercher par titre, auteur ou genre…"
|
|
|
|
|
value={query}
|
|
|
|
|
onChange={(e) => onQueryChange(e.target.value)}
|
|
|
|
|
aria-label="Recherche"
|
|
|
|
|
/>
|
|
|
|
|
<div className="filter-tabs" role="tablist">
|
|
|
|
|
{TABS.map(([value, label]) => (
|
|
|
|
|
<button
|
|
|
|
|
key={value}
|
|
|
|
|
type="button"
|
|
|
|
|
role="tab"
|
|
|
|
|
aria-selected={filter === value}
|
|
|
|
|
className={filter === value ? 'tab active' : 'tab'}
|
|
|
|
|
onClick={() => onFilterChange(value)}
|
|
|
|
|
>
|
|
|
|
|
{label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{filtered.length === 0 ? (
|
|
|
|
|
<p className="empty-state">
|
|
|
|
|
{books.length === 0
|
|
|
|
|
? 'Aucun livre enregistré. Utilisez le formulaire à gauche : Je veux enregistrer un nouveau livre (POST /api/books).'
|
|
|
|
|
: 'Aucun résultat pour ces critères.'}
|
|
|
|
|
</p>
|
|
|
|
|
) : (
|
|
|
|
|
<ul className="book-grid">
|
|
|
|
|
{filtered.map((book) => (
|
|
|
|
|
<li key={book.id} className="book-card">
|
|
|
|
|
<div className="book-card-top">
|
|
|
|
|
<span className={`badge ${book.read ? 'read' : 'unread'}`}>
|
|
|
|
|
{book.read ? 'Lu' : 'À lire'}
|
|
|
|
|
</span>
|
|
|
|
|
{book.genre ? <span className="genre">{book.genre}</span> : null}
|
|
|
|
|
</div>
|
|
|
|
|
<h3>
|
|
|
|
|
<Link className="book-title-link" to={`/${book.id}`}>
|
|
|
|
|
{book.title}
|
|
|
|
|
</Link>
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="author">{book.author}</p>
|
2026-04-25 15:40:55 +02:00
|
|
|
<p className="year">
|
|
|
|
|
{book.year} · {Number(book.price ?? 10).toFixed(2)} €
|
|
|
|
|
</p>
|
2026-03-22 18:45:37 +01:00
|
|
|
<div className="card-actions">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="btn small"
|
|
|
|
|
onClick={() => onToggleRead(book.id)}
|
|
|
|
|
>
|
|
|
|
|
{book.read ? 'Marquer non lu' : 'Marquer lu'}
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="btn small danger"
|
|
|
|
|
onClick={() => onRemove(book.id)}
|
|
|
|
|
>
|
|
|
|
|
Supprimer
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
)}
|
|
|
|
|
</section>
|
|
|
|
|
)
|
|
|
|
|
}
|