diff --git a/src/components/BooksTable.jsx b/src/components/BooksTable.jsx
new file mode 100644
index 0000000..0c1660d
--- /dev/null
+++ b/src/components/BooksTable.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import {
+ Table,
+ TableHead,
+ TableBody,
+ TableRow,
+ TableCell,
+ TableContainer,
+ TableSortLabel,
+ Paper
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import BookTableRow from './BookTableRow';
+
+const BooksTable = ({
+ rows,
+ loading,
+ sortOrder,
+ sortingColumn,
+ onSort,
+ onRemoveBook,
+ onAddToLibrary,
+ onOpenReviewModal,
+ onOpenReadReviewModal,
+ children
+}) => {
+ const { t } = useTranslation();
+ const columns = [
+ { id: 'title', label: 'title' },
+ { id: 'author', label: 'author' },
+ { id: 'category', label: 'category' },
+ { id: 'publisher', label: 'publisher' },
+ { id: 'ISBN', label: 'isbn' },
+ { id: 'year', label: 'year' },
+ { id: 'edition', label: 'edition' }
+ ];
+
+ return (
+
+
+
+
+ {t('home.headings.action')}
+ {columns.map((column) => (
+
+ onSort(column.id, sortOrder ? 'asc' : 'desc')}
+ >
+ {t(`home.headings.${column.label}`)}
+
+
+ ))}
+ {t('home.headings.review')}
+
+
+
+ {children}
+ {rows.map((row, idx) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default BooksTable;
\ No newline at end of file
diff --git a/src/components/Library/Library.jsx b/src/components/Library/Library.jsx
index eda5810..fbc5459 100644
--- a/src/components/Library/Library.jsx
+++ b/src/components/Library/Library.jsx
@@ -1,1155 +1,96 @@
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import { orderBy } from 'lodash';
-import Box from '@mui/material/Box';
-import Table from '@mui/material/Table';
-import TableBody from '@mui/material/TableBody';
-import CircularProgress from '@mui/material/CircularProgress';
-import TableCell from '@mui/material/TableCell';
-import TableContainer from '@mui/material/TableContainer';
-import TableHead from '@mui/material/TableHead';
-import TableRow from '@mui/material/TableRow';
-import StarIcon from '@mui/icons-material/Star';
-import Paper from '@mui/material/Paper';
-import IconButton from '@mui/material/IconButton';
-import TableSortLabel from '@mui/material/TableSortLabel';
-import Typography from '@mui/material/Typography';
-import AddIcon from '@mui/icons-material/Add';
-import DeleteIcon from '@mui/icons-material/Delete';
-// import RemoveIcon from '@mui/icons-material/Remove';
-import Dialog from '@mui/material/Dialog';
-import DialogActions from '@mui/material/DialogActions';
-import DialogContent from '@mui/material/DialogContent';
-import DialogContentText from '@mui/material/DialogContentText';
-import DialogTitle from '@mui/material/DialogTitle';
-import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
-import EditIcon from '@mui/icons-material/Edit';
-import ShareIcon from '@mui/icons-material/Share';
import { useTranslation } from 'react-i18next';
-/* import { TableVirtuoso } from 'react-virtuoso'; */
-import './Library.css';
-import Classes from './Library.module.css';
-// import rows from './data.json';
-import { Button, Modal, TextField } from '@mui/material';
-import CloseIcon from '@mui/icons-material/Close';
-import Snackbar from '@mui/material/Snackbar';
-import ISBN from 'isbn-validate';
-import { Rating } from 'react-simple-star-rating';
-import axios from 'axios';
-export default function Library({ filter, setFilter }) {
- const [myRows, setMyRows] = useState([]);
- const [visibleContent, setVisibleContent] = useState(-1);
- const [showSnackBar, handleSnackBar] = useState(false);
- const [isDeleted, setIsDeleted] = useState(false);
- const [removedItemName, setRemovedItemName] = useState('');
-
- //table sorting states
- const [sortOrder, setSortOrder] = useState(false);
- const [sortingColumn, setSortingColumn] = useState('');
-
- //Modal states
- const [showModal, handleModalBox] = useState(false);
- const [loading, setLoading] = useState(false);
- const [bookUploadError, setBookUploadError] = useState('');
- const [name, setName] = useState('');
- const [author, setAuthor] = useState('');
- const [category, setCategory] = useState('');
- const [publisher, setPublisher] = useState('');
- const [isbn, setIsbn] = useState('');
- const [year, setYear] = useState('');
- const [edition, setEdition] = useState('');
- const [error, setError] = useState(false);
- const [blankEntry, setBlankEntry] = useState(false);
+import { CircularProgress } from '@mui/material';
+import AddBookButton from './components/AddBookButton';
+import BooksTable from './components/BooksTable';
+import { useLibrary } from './hooks/useLibrary';
+import { useModals } from './hooks/useModals';
+import {
+ AddBookModal,
+ ReviewModal,
+ ReadReviewModal,
+ LibraryAddModal
+} from './components/Modals';
+import NotificationSnackbar from './components/NotificationSnackbar';
- //Book review modal states
- const [showReviewModal, handleReviewModal] = useState(false);
- const [isReviewAdded, handleReviewAdded] = useState(false);
- const [bookName, setBookName] = useState('');
- const [bookReview, setBookReview] = useState('');
- const [rating, setRating] = useState(0);
-
- //Book read review modal states
- const [enableReviewModal, setEnableReviewModal] = useState(false);
- const [book, setBook] = useState({});
- //get loggedIn user email to send with bookObj for userId on new book entry
- const userEmail = JSON.parse(localStorage.getItem('user')).email;
- //get loggedIn user id to match with book userId
- const userId = JSON.parse(localStorage.getItem('user'))._id;
- //This state helps us for two way in input filed
- const [isAdded, handleIsAdded] = useState(false);
+export default function Library({ filter, setFilter }) {
const { t } = useTranslation();
- //Add to library modal
- const [isLibraryModalOpen, setIsLibraryModalOpen] = useState(false);
- // prettier-ignore
- const [addToPersonalLibraryMessage, setAddToPersonalLibraryMessage] = useState(null);
- const removeBookByName = async (row) => {
- try {
- setLoading(true);
- const response = await axios.delete(
- `http://localhost:3001/api/books/${row._id}`,
- {
- data: {
- bookId: row._id,
- userId: row.userId,
- userEmail,
- },
- }
- );
- if (response.status === 200) {
- setRemovedItemName('removed ' + row.title + ' successfully');
- setVisibleContent(-1);
- fetchBooksFromDB();
- setIsDeleted(true);
- }
- // You can also update your state here to reflect the changes
- } catch (err) {
- setLoading(false);
- const errorMsg = err.response.data.message;
- setBookUploadError(errorMsg);
- handleSnackBar(true);
- }
-
- setLoading(false);
- };
-
- const addItemToTable = async (e) => {
- e.preventDefault();
- try {
- setLoading(true);
- if (
- name &&
- author &&
- category &&
- publisher &&
- isbn &&
- year &&
- edition
- ) {
- let bookObj = {
- name,
- author,
- category,
- publisher,
- isbn,
- year,
- edition,
- reviews: [],
- userEmail,
- };
-
- const response = await axios.post(
- 'http://localhost:3001/api/books/newbook',
- { bookObj }
- );
-
- if (response.status === 200) {
- handleIsAdded(true);
- resetBookState();
- handleModalBox(false);
- fetchBooksFromDB();
- }
- } else {
- setBlankEntry(true);
- }
- } catch (err) {
- const errorMsg = err.response.data.message;
- setBookUploadError(errorMsg);
- handleSnackBar(true);
- }
- setLoading(false);
- };
-
- const addBookToPersonalLibrary = async (book) => {
- try {
- const response = await axios.post(
- 'http://localhost:3001/api/books/add-book-to-personal-library',
- { book }
- );
-
- setAddToPersonalLibraryMessage(response.data.payload);
- setIsLibraryModalOpen(true);
- } catch (error) {
- console.log(error);
- }
- };
-
- const addBooksToDB = async (fileBooks) => {
- try {
- const userData = JSON.parse(localStorage.getItem('user'));
-
- // Check if userData is not null before accessing its properties
- if (userData) {
- // Iterate through each book in the file
- for (const bookObj of fileBooks) {
- const userId = userData._id;
- const userEmail = userData.email;
-
- const {
- title,
- author,
- category,
- publisher,
- ISBN,
- year,
- edition,
- } = bookObj;
- console.log('Adding book', bookObj);
-
- // Send a request to add the book to the backend
- await axios.post(
- 'http://localhost:3001/api/books/newbook',
- {
- bookObj: {
- userId,
- userEmail,
- name: title,
- author,
- category,
- publisher,
- isbn: ISBN,
- year,
- edition,
- reviews: [],
- },
- }
- );
- }
-
- // After adding all books, fetch books from the backend
- fetchBooksFromDB();
- } else {
- console.error('Error adding books from file:', error);
- }
- } catch (error) {
- // Handle error
- console.error('Error adding books from file:', error);
- }
- };
-
- const fetchBooksFromDB = async () => {
- try {
- setLoading(true);
-
- // Fetch data from the database
- const response = await axios.get(
- 'http://localhost:3001/api/books/getall'
- );
- const books = response.data;
- console.log(books);
- // If the database has no books, fetch data from the file
- if (books.length === 0) {
- const fileResponse = await axios.get(
- 'http://localhost:3001/api/books/filedata'
- );
- const fileBooks = fileResponse.data;
-
- // Assuming fileBooks is an array of books from the file
- setMyRows([...fileBooks]);
-
- // Add books to the database
- await addBooksToDB(fileBooks);
- } else {
- // Database has books, use them
- setMyRows([...books]);
- }
- } catch (error) {
- // Handle error
- console.error('Error fetching books:', error);
- } finally {
- setLoading(false);
- }
- };
-
- const resetBookState = () => {
- setName('');
- setAuthor('');
- setCategory('');
- setPublisher('');
- setIsbn('');
- setYear('');
- setEdition('');
- };
-
- const closeSnackBar = () => {
- handleSnackBar(false);
- handleIsAdded(false);
- handleReviewAdded(false);
- setBlankEntry(false);
- setIsDeleted(false);
- };
- const action = (
-
-
-
-
-
- );
-
- const style = {
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- width: 400,
- bgcolor: 'background.paper',
- border: '2px solid #000',
- boxShadow: 24,
- p: 4,
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'space-between',
- alignItems: 'center',
- };
-
- //Check if publishing year is a four digit number
- const handleYearChange = (event) => {
- const value = event.target.value;
- setYear(value);
-
- if (value.length === 4 && !isNaN(value)) {
- setError(false);
- } else {
- setError(true);
- }
- };
-
- // books table sort
- const handleSort = (columnName, order) => {
- if (columnName === 'title') {
- const sortedRows = orderBy(myRows, ['title'], [order]);
- setMyRows(sortedRows);
- } else if (columnName === 'author') {
- const sortedRows = orderBy(myRows, ['author'], [order]);
- setMyRows(sortedRows);
- } else if (columnName === 'category') {
- const sortedRows = orderBy(myRows, ['category'], [order]);
- setMyRows(sortedRows);
- } else if (columnName === 'publisher') {
- const sortedRows = orderBy(myRows, ['publisher'], [order]);
- setMyRows(sortedRows);
- } else if (columnName === 'year') {
- const sortedRows = orderBy(myRows, ['year'], [order]);
- setMyRows(sortedRows);
- } else if (columnName === 'ISBN') {
- const sortedRows = orderBy(myRows, ['ISBN'], [order]);
- setMyRows(sortedRows);
- } else if (columnName === 'edition') {
- const sortedRows = orderBy(myRows, ['edition'], [order]);
- setMyRows(sortedRows);
- }
- };
-
- //Check if ISBN is valid
- const handleISBNChange = (event) => {
- const value = event.target.value;
- setIsbn(value);
-
- if (ISBN.Validate(value)) {
- setError(false);
- } else {
- setError(true);
- }
- };
-
- //adds the review for each book
- const handleBookReview = () => {
- const updatedRows = myRows.map((book) => {
- if (book.name === bookName) {
- return {
- ...book,
- reviews: [
- ...book.reviews,
- { starRating: rating, textReview: bookReview },
- ],
- };
- } else {
- return { ...book };
- }
- });
- setMyRows([...updatedRows]);
- handleReviewAdded(true);
- setBookReview('');
- setRating(0);
- };
-
- const onPointerMove = (value, index) => {
- setRating(value);
- };
-
- const starGenerator = (count) => {
- const stars = [];
- for (let i = 1; i <= count; i++) {
- stars.push();
- }
- return stars;
- };
-
- React.useEffect(() => {
- const filteredRow = () => {
- if (filter) {
- //searching can be improved
- const newRow = myRows.filter(
- (row) =>
- (filter && row.title.includes(filter)) ||
- (row.category && row.category.includes(filter)) ||
- row.author.includes(filter)
- );
-
- if (newRow.length > 0) {
- setMyRows(newRow);
- } else {
- setMyRows(myRows);
- }
- } else {
- setMyRows(myRows);
- }
- };
- filteredRow();
- return () => {
- setMyRows([]);
- setFilter('');
- };
- }, [filter, setFilter, myRows]);
- //remove the empty dependency array
- useEffect(() => {
- fetchBooksFromDB();
- }, []);
-
- // open hidden content when ellipsis icon is clicked
- const handleShowMore = (index) => {
- setVisibleContent((prevIndex) => (prevIndex === index ? -1 : index));
- };
+ const {
+ myRows,
+ loading,
+ handleSort,
+ sortOrder,
+ sortingColumn,
+ setSortOrder,
+ setSortingColumn,
+ removeBookByName,
+ addItemToTable,
+ addBookToPersonalLibrary
+ } = useLibrary(filter, setFilter);
+
+ const {
+ notifications,
+ modalsState,
+ modalActions,
+ reviewState,
+ handleBookReview,
+ onPointerMove
+ } = useModals();
+
+ if (!myRows.length) {
+ return (
+
+
+
+ );
+ }
return (
<>
-
-
-
+
+
-
- {/* //book review modal start// */}
- {
- handleReviewModal(false);
- }}
- >
-
-
- {t('modal.review.title')} {bookName}
-
-
+
- {
- setBookReview(e.target.value);
- }}
- />
+
- {isReviewAdded ? (
- <>
-
- >
- ) : (
- <>
-
- >
- )}
-
-
- {/* //book review modal end// //book read review modal start// */}
-
-
-
- {/* //book read reviews modal end// */}
- {
- handleModalBox(false);
- }}
+
-
-
- {t('home.headings.item')}
-
-
-
-
- {/* Add to Library modal */}
- {
- setIsLibraryModalOpen(false);
- }}
- >
- {/* prettier-ignore */}
-
-
- { addToPersonalLibraryMessage? 'Book successfully added to personal library':'Book already exist in personal library' }
-
-
-
-
-
-
- {myRows.length > 0 ? (
-
-
-
-
-
- {t('home.headings.action')}
-
-
- {
- setSortOrder(!sortOrder);
- setSortingColumn('title');
- handleSort(
- 'title',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.title')}
-
-
-
- {
- setSortOrder(!sortOrder);
- setSortingColumn('author');
- handleSort(
- 'author',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.author')}
- {' '}
-
-
- {' '}
- {
- setSortOrder(!sortOrder);
- setSortingColumn('category');
- handleSort(
- 'category',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.category')}
- {' '}
-
-
- {' '}
- {
- setSortOrder(!sortOrder);
- setSortingColumn('publisher');
- handleSort(
- 'publisher',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.publisher')}
- {' '}
-
-
- {' '}
- {
- setSortOrder(!sortOrder);
- setSortingColumn('ISBN');
- handleSort(
- 'ISBN',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.isbn')}
-
-
-
- {' '}
- {
- setSortOrder(!sortOrder);
- setSortingColumn('year');
- handleSort(
- 'year',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.year')}
- {' '}
-
-
- {
- setSortOrder(!sortOrder);
- setSortingColumn('edition');
- handleSort(
- 'edition',
- sortOrder ? 'asc' : 'desc'
- );
- }}
- >
- {t('home.headings.edition')}
-
-
-
- {t('home.headings.review')}
-
-
-
-
-
-
-
-
-
- {myRows.map((row, idx) => (
-
- {userId === row.userId ? (
-
-
- handleShowMore(idx)
- }
- >
-
-
-
-
- {t('home.more')}
-
-
-
-
- setVisibleContent(-1)
- }
- >
- {t('home.buttons.close')}
-
-
- {
- removeBookByName(row);
- }}
- >
- {t('home.buttons.delete')}
- {loading ? (
-
- ) : (
-
- )}
-
-
- {t('home.buttons.edit')}
-
-
-
- {t('home.buttons.share')}
-
-
-
-
- ) : (
-
- )}
-
- {row.title}
-
-
- {row.author}
-
-
- {row.category}
-
-
- {row.publisher}
-
-
- {row.ISBN}
-
-
- {row.year}
-
-
- {row.edition + ' Edition'}
-
-
-
-
-
-
-
- ))}
-
-
-
- ) : (
-
-
-
- )}
+ modalActions.handleModalBox(true)} />
+
>
);
}
@@ -1157,4 +98,4 @@ export default function Library({ filter, setFilter }) {
Library.propTypes = {
filter: PropTypes.string,
setFilter: PropTypes.func,
-};
+};
\ No newline at end of file
diff --git a/src/components/Library/hooks/useLibrary.jsx b/src/components/Library/hooks/useLibrary.jsx
new file mode 100644
index 0000000..1a3b382
--- /dev/null
+++ b/src/components/Library/hooks/useLibrary.jsx
@@ -0,0 +1,128 @@
+import { useState, useEffect } from 'react';
+import { orderBy } from 'lodash';
+import axios from 'axios';
+
+export const useLibrary = (filter, setFilter) => {
+ const [myRows, setMyRows] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [sortOrder, setSortOrder] = useState(false);
+ const [sortingColumn, setSortingColumn] = useState('');
+
+ const handleSort = (columnName, order) => {
+ const sortConfig = {
+ title: 'title',
+ author: 'author',
+ category: 'category',
+ publisher: 'publisher',
+ year: 'year',
+ ISBN: 'ISBN',
+ edition: 'edition'
+ };
+
+ if (sortConfig[columnName]) {
+ const sortedRows = orderBy(myRows, [sortConfig[columnName]], [order]);
+ setMyRows(sortedRows);
+ }
+ };
+
+ const removeBookByName = async (row) => {
+ try {
+ setLoading(true);
+ const response = await axios.delete(
+ `http://localhost:3001/api/books/${row._id}`,
+ {
+ data: {
+ bookId: row._id,
+ userId: row.userId,
+ userEmail: JSON.parse(localStorage.getItem('user')).email,
+ },
+ }
+ );
+ if (response.status === 200) {
+ fetchBooksFromDB();
+ }
+ } catch (err) {
+ console.error('Error removing book:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const addItemToTable = async (bookData) => {
+ try {
+ setLoading(true);
+ const response = await axios.post(
+ 'http://localhost:3001/api/books/newbook',
+ { bookObj: bookData }
+ );
+ if (response.status === 200) {
+ fetchBooksFromDB();
+ return true;
+ }
+ } catch (err) {
+ console.error('Error adding book:', err);
+ return false;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const addBookToPersonalLibrary = async (book) => {
+ try {
+ const response = await axios.post(
+ 'http://localhost:3001/api/books/add-book-to-personal-library',
+ { book }
+ );
+ return response.data.payload;
+ } catch (error) {
+ console.error('Error adding to personal library:', error);
+ return null;
+ }
+ };
+
+ const fetchBooksFromDB = async () => {
+ try {
+ setLoading(true);
+ const response = await axios.get('http://localhost:3001/api/books/getall');
+ setMyRows(response.data);
+ } catch (error) {
+ console.error('Error fetching books:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchBooksFromDB();
+ }, []);
+
+ useEffect(() => {
+ if (filter) {
+ const filteredRows = myRows.filter(
+ (row) =>
+ row.title.includes(filter) ||
+ (row.category && row.category.includes(filter)) ||
+ row.author.includes(filter)
+ );
+ setMyRows(filteredRows.length > 0 ? filteredRows : myRows);
+ }
+
+ return () => {
+ setMyRows([]);
+ setFilter('');
+ };
+ }, [filter, setFilter]);
+
+ return {
+ myRows,
+ loading,
+ handleSort,
+ sortOrder,
+ sortingColumn,
+ setSortOrder,
+ setSortingColumn,
+ removeBookByName,
+ addItemToTable,
+ addBookToPersonalLibrary
+ };
+};
\ No newline at end of file
diff --git a/src/components/Library/hooks/useModals.jsx b/src/components/Library/hooks/useModals.jsx
new file mode 100644
index 0000000..0348998
--- /dev/null
+++ b/src/components/Library/hooks/useModals.jsx
@@ -0,0 +1,57 @@
+import { useState } from 'react';
+
+export const useModals = () => {
+ const [notifications, setNotifications] = useState({
+ isDeleted: false,
+ isAdded: false,
+ blankEntry: false,
+ showSnackBar: false,
+ removedItemName: '',
+ bookUploadError: ''
+ });
+
+ const [modalsState, setModalsState] = useState({
+ showModal: false,
+ showReviewModal: false,
+ enableReviewModal: false,
+ isLibraryModalOpen: false,
+ addToPersonalLibraryMessage: null
+ });
+
+ const [reviewState, setReviewState] = useState({
+ bookName: '',
+ bookReview: '',
+ rating: 0,
+ book: {}
+ });
+
+ const modalActions = {
+ handleModalBox: (show) => setModalsState(prev => ({ ...prev, showModal: show })),
+ handleReviewModal: (show) => setModalsState(prev => ({ ...prev, showReviewModal: show })),
+ setEnableReviewModal: (show) => setModalsState(prev => ({ ...prev, enableReviewModal: show })),
+ setIsLibraryModalOpen: (show) => setModalsState(prev => ({ ...prev, isLibraryModalOpen: show }))
+ };
+
+ const handleBookReview = (review) => {
+ setReviewState(prev => ({
+ ...prev,
+ bookReview: review
+ }));
+ };
+
+ const onPointerMove = (value) => {
+ setReviewState(prev => ({
+ ...prev,
+ rating: value
+ }));
+ };
+
+ return {
+ notifications,
+ modalsState,
+ modalActions,
+ reviewState,
+ handleBookReview,
+ onPointerMove
+ };
+};
\ No newline at end of file