book.js

mail@pastecode.io avatar
unknown
plain_text
5 months ago
12 kB
2
Indexable
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