book.js
unknown
plain_text
a month ago
12 kB
2
Indexable
Never
import React, { useEffect, useState } from "react"; import { useLocation, useNavigate, Link } from "react-router-dom"; import Navbar from "../components/Navbar"; import logo from "../assets/images/logoweb.png"; import Footer from "../components/Footer"; import Loader from "../components/Loader"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowLeft, faArrowRight, faTimes } from "@fortawesome/free-solid-svg-icons"; import "../pages/Books.css"; import { BASE_URL } from './apiConfig'; const ProductList = () => { const [categories, setCategories] = useState([]); const [authors, setAuthors] = useState([]); const [books, setBooks] = useState([]); const [filteredBooks, setFilteredBooks] = useState([]); const [selectedRatings, setSelectedRatings] = useState([]); const [selectedGenres, setSelectedGenres] = useState([]); const [selectedAuthors, setSelectedAuthors] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [loading, setLoading] = useState(true); const [sortBy, setSortBy] = useState("title"); const [sortDir, setSortDir] = useState("ASC"); const booksPerPage = 10; const location = useLocation(); const navigate = useNavigate(); useEffect(() => { const fetchInitialData = async () => { try { await fetchData(); await fetchAuthors(); const searchParams = new URLSearchParams(location.search); const sortByParam = searchParams.get("sortBy") || "title"; const sortDirParam = searchParams.get("sortDir") || "ASC"; const pageParam = parseInt(searchParams.get("page")) || 1; setSortBy(sortByParam); setSortDir(sortDirParam); setCurrentPage(pageParam); fetchBooks(pageParam - 1, sortByParam, sortDirParam); } catch (error) { console.error("Error fetching initial data:", error); } }; fetchInitialData(); }, [location.search]); useEffect(() => { filterBooks(); }, [selectedGenres, selectedAuthors, selectedRatings, books]); const fetchData = async () => { try { const response = await fetch(`${BASE_URL}/api/public/categories/getall`, { headers: { "ngrok-skip-browser-warning": "69420" } }); if (!response.ok) throw new Error("Error fetching categories"); const data = await response.json(); setCategories(data); } catch (error) { console.error("Error fetching categories:", error); } }; const fetchBooks = async (page = 0, sortBy = "title", sortDir = "ASC") => { setLoading(true); try { const response = await fetch(`${BASE_URL}/api/public/books/pagedAndSorted?page=${page}&size=${booksPerPage}&sortBy=${sortBy}&sortDir=${sortDir}`, { headers: { "ngrok-skip-browser-warning": "69420" } }); if (!response.ok) throw new Error("Error fetching books"); const data = await response.json(); const booksWithRatings = await Promise.all( data.content.map(async (book) => { const rating = await fetchRating(book.id); return { ...book, averageRating: rating.averageRating }; }) ); setBooks(booksWithRatings); setFilteredBooks(booksWithRatings); setTotalPages(data.totalPages); } catch (error) { console.error("Error fetching books:", error); } finally { setLoading(false); } }; const fetchRating = async (bookId) => { try { const response = await fetch(`${BASE_URL}/api/public/review/ave-rat/${bookId}/average-rating`, { headers: { "ngrok-skip-browser-warning": "69420" } }); if (!response.ok) throw new Error("Error fetching rating"); return await response.json(); } catch (error) { console.error("Error fetching rating:", error); return { averageRating: 0 }; } }; const fetchAuthors = async () => { try { const response = await fetch(`${BASE_URL}/api/public/author/getAll`, { headers: { "ngrok-skip-browser-warning": "69420" } }); if (!response.ok) throw new Error("Error fetching authors"); const data = await response.json(); setAuthors(data); } catch (error) { console.error("Error fetching authors:", error); } }; const filterBooks = () => { let filtered = [...books]; if (selectedGenres.length > 0) { filtered = filtered.filter((book) => selectedGenres.includes(book.category.name)); } if (selectedAuthors.length > 0) { filtered = filtered.filter((book) => selectedAuthors.includes(book.author.fullName)); } if (selectedRatings.length > 0) { filtered = filtered.filter((book) => book.averageRating >= selectedRatings[0]); } setFilteredBooks(filtered); }; const handleCategoryClick = (categoryId) => { const category = categories.find((cat) => cat.id === categoryId); if (category) { setSelectedGenres((prevGenres) => prevGenres.includes(category.name) ? prevGenres.filter((g) => g !== category.name) : [...prevGenres, category.name] ); updateURL({ genre: category.name }); } }; const handleAuthorClick = (authorId) => { const author = authors.find((auth) => auth.id === authorId); if (author) { setSelectedAuthors((prevAuthors) => prevAuthors.includes(author.fullName) ? prevAuthors.filter((a) => a !== author.fullName) : [...prevAuthors, author.fullName] ); updateURL({ author: author.fullName }); } }; const handleRatingClick = (rating) => { setSelectedRatings((prevRatings) => prevRatings.length > 0 && prevRatings[0] === rating ? [] : [rating] ); updateURL({ rating }); }; const handlePageChange = (page) => { setCurrentPage(page); updateURL({ page }); }; const handleRemoveFilter = (type, value) => { if (type === "genre") { setSelectedGenres((prevGenres) => prevGenres.filter((g) => g !== value)); } else if (type === "author") { setSelectedAuthors((prevAuthors) => prevAuthors.filter((a) => a !== value)); } else if (type === "rating") { setSelectedRatings([]); } updateURL({}); }; const renderRating = (rating) => { const fullStars = Math.floor(rating); const emptyStars = 5 - fullStars; return ( <> {"★".repeat(fullStars)} {"☆".repeat(emptyStars)} </> ); }; const handleClearAll = () => { setSelectedRatings([]); setSelectedGenres([]); setSelectedAuthors([]); updateURL({}); }; const updateURL = (params) => { const searchParams = new URLSearchParams(location.search); Object.keys(params).forEach(key => { if (params[key] !== undefined && params[key] !== null) { searchParams.set(key, params[key]); } else { searchParams.delete(key); } }); navigate({ search: searchParams.toString() }); }; if (loading) { return <Loader />; } return ( <div className="app-container"> <Navbar /> <div className="content-wrapper"> <div className="sidebar-list"> <img className="logo" src={logo} alt="Logo" style={{ width: "130px", height: "70px" }} /> <div className="rating-filter"> <h2>Ratings</h2> {[4, 3, 2, 1].map((rating) => ( <p key={rating} onClick={() => handleRatingClick(rating)}> {renderRating(rating)} & Up </p> ))} </div> <div className="genre-list"> <h2>Genre</h2> {categories.map((category) => ( <button key={category.id} onClick={() => handleCategoryClick(category.id)} className={ selectedGenres.includes(category.name) ? "active" : "" } > {category.name} </button> ))} </div> <div className="author-list"> <h2>Authors</h2> {authors.map((author) => ( <button key={author.id} onClick={() => handleAuthorClick(author.id)} className={ selectedAuthors.includes(author.fullName) ? "active" : "" } > {author.fullName} </button> ))} </div> </div> <div className="main-list"> <h2 className="list-title">Product List</h2> <div className="filtered-by"> <p>Filtered By:</p> <div className="filtered-options"> {selectedGenres.map((genre) => ( <button key={genre} onClick={() => handleRemoveFilter("genre", genre)} > {genre}{" "} <FontAwesomeIcon icon={faTimes} className="remove-icon" /> </button> ))} {selectedAuthors.map((author) => ( <button key={author} onClick={() => handleRemoveFilter("author", author)} > {author}{" "} <FontAwesomeIcon icon={faTimes} className="remove-icon" /> </button> ))} {selectedRatings.map((rating) => ( <button key={rating} onClick={() => handleRemoveFilter("rating")} > {renderRating(rating)} & Up{" "} <FontAwesomeIcon icon={faTimes} className="remove-icon" /> </button> ))} </div> <p className="clear-all" onClick={handleClearAll}> Clear All </p> </div> <div className="books-list"> {filteredBooks.length > 0 ? ( filteredBooks.map((book) => ( <div key={book.id} className="booklist"> <Link to={`/book/${book.id}`} className="book-link"> <img src={book.cover} alt={book.title} className="product-image" /> <h3 className="book-title">{book.title}</h3> <p className="book-rating"> {renderRating(book.averageRating)} </p> </Link> </div> )) ) : ( <p>No books found.</p> )} </div> <div className="pagination"> <button className="prev" onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1} > <FontAwesomeIcon icon={faArrowLeft} /> </button> {Array.from({ length: totalPages }, (_, i) => ( <button key={i + 1} onClick={() => handlePageChange(i + 1)} className={currentPage === i + 1 ? "active" : ""} > {i + 1} </button> ))} <button className="next" onClick={() => handlePageChange(currentPage + 1)} disabled={currentPage === totalPages} > <FontAwesomeIcon icon={faArrowRight} /> </button> </div> </div> </div> <Footer /> </div> ); }; export default ProductList;
Leave a Comment