aliases
This commit is contained in:
@@ -1,36 +1,43 @@
|
||||
// packages/frontend/src/components/CelebrityList.jsx
|
||||
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { getCelebrities, deleteCelebrity } from '../services/api';
|
||||
import './CelebrityList.css'; // Importa il nuovo CSS
|
||||
import './CelebrityList.css';
|
||||
|
||||
// Icone SVG semplici per le azioni
|
||||
const EditIcon = () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>;
|
||||
const DeleteIcon = () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>;
|
||||
const MoreIcon = () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>;
|
||||
|
||||
|
||||
function CelebrityList() {
|
||||
const [celebrities, setCelebrities] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
// Stato per la paginazione
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Effetto per "debouncare" il termine di ricerca
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
}, 300); // Attende 300ms dopo l'ultima digitazione prima di aggiornare
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [searchTerm]);
|
||||
|
||||
// Effetto per caricare i dati quando il termine di ricerca (debounced) cambia
|
||||
useEffect(() => {
|
||||
fetchCelebrities();
|
||||
}, []);
|
||||
}, [debouncedSearchTerm]);
|
||||
|
||||
const fetchCelebrities = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await getCelebrities();
|
||||
const data = await getCelebrities(debouncedSearchTerm);
|
||||
setCelebrities(data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
@@ -51,24 +58,18 @@ function CelebrityList() {
|
||||
}
|
||||
};
|
||||
|
||||
// Memoizzazione dei dati filtrati e paginati per performance
|
||||
const filteredCelebrities = useMemo(() => {
|
||||
return celebrities.filter(c =>
|
||||
c.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
}, [celebrities, searchTerm]);
|
||||
|
||||
// La paginazione ora opera sulla lista (già filtrata) ricevuta dal backend
|
||||
const paginatedCelebrities = useMemo(() => {
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
return filteredCelebrities.slice(startIndex, startIndex + itemsPerPage);
|
||||
}, [filteredCelebrities, currentPage, itemsPerPage]);
|
||||
return celebrities.slice(startIndex, startIndex + itemsPerPage);
|
||||
}, [celebrities, currentPage, itemsPerPage]);
|
||||
|
||||
const totalPages = Math.ceil(filteredCelebrities.length / itemsPerPage);
|
||||
const totalPages = Math.ceil(celebrities.length / itemsPerPage);
|
||||
|
||||
const renderPagination = () => (
|
||||
<div className="pagination-controls">
|
||||
<span>
|
||||
Pagina {currentPage} di {totalPages}
|
||||
Pagina {currentPage} di {totalPages} ({celebrities.length} risultati)
|
||||
</span>
|
||||
<div className="grid">
|
||||
<button onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1}>
|
||||
@@ -89,8 +90,8 @@ function CelebrityList() {
|
||||
const renderEmptyState = () => (
|
||||
<div className="empty-state">
|
||||
<h3>Nessun Profilo Trovato</h3>
|
||||
<p>La tua lista è vuota. Inizia aggiungendo una nuova celebrità.</p>
|
||||
<Link to="/celebrity/new" role="button" className="primary">+ Aggiungi la prima celebrità</Link>
|
||||
<p>{searchTerm ? "Prova a modificare i termini della ricerca." : "Inizia aggiungendo una nuova celebrità."}</p>
|
||||
<Link to="/celebrity/new" role="button" className="primary">+ Aggiungi una celebrità</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -106,18 +107,18 @@ function CelebrityList() {
|
||||
<div className="list-controls">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Cerca per nome..."
|
||||
placeholder="Cerca per nome o alias..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => { setSearchTerm(e.target.value); setCurrentPage(1); }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{loading && <article aria-busy="true">Caricamento in corso...</article>}
|
||||
{loading && <article aria-busy="true">Ricerca in corso...</article>}
|
||||
{error && <p className="error-message">Errore: {error}</p>}
|
||||
|
||||
{!loading && !error && (
|
||||
<>
|
||||
{celebrities.length === 0 ? renderEmptyState() : (
|
||||
{paginatedCelebrities.length === 0 ? renderEmptyState() : (
|
||||
<>
|
||||
<div className="table-responsive">
|
||||
<table>
|
||||
@@ -135,7 +136,7 @@ function CelebrityList() {
|
||||
{paginatedCelebrities.map((celeb) => (
|
||||
<tr key={celeb.id}>
|
||||
<td>
|
||||
<img src={`https://i.pravatar.cc/50?u=${celeb.id}`} alt={celeb.name} className="avatar-image" />
|
||||
<img src={celeb.profile_image ? `/api/uploads/${celeb.profile_image.file_path}` : `https://i.pravatar.cc/50?u=${celeb.id}`} alt={celeb.name} className="avatar-image" />
|
||||
</td>
|
||||
<td>
|
||||
<Link to={`/celebrity/${celeb.id}`} className="celeb-link">
|
||||
|
||||
Reference in New Issue
Block a user