import React, { useCallback, useContext, useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Book } from "../../types/Book"; import { Button, Form, Table } from "react-bootstrap"; import { createColumnHelper, flexRender, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, PaginationState, useReactTable, ColumnDef, } from "@tanstack/react-table"; import { Actions } from "./components/Actions"; import { ImArrowDown, ImArrowUp } from "react-icons/im"; import { useLocation } from "react-router-dom"; import { format } from "date-fns"; import { useAuth } from "../../shared/utils/useAuthentication"; import { ModalSelector } from "../../shared/components/modals/Modals"; import { ModalContext } from "../../App"; import { Pagination } from "./components/Pagination"; import { modalTypes } from "../../shared/components/modals/types"; export const Library = (): React.JSX.Element => { const location = useLocation(); const locationState = location.state; const params = useMemo( () => new URLSearchParams(location.search), [location] ); const { authenticated } = useAuth(); const modalContext = useContext(ModalContext); const [selectedBooks, setSelectedBooks] = useState<Book["uuid"][]>([]); const toggleBookSelection = useCallback( (uuid: Book["uuid"]) => setSelectedBooks((prev) => prev.includes(uuid) ? prev.filter((current) => current !== uuid) : [...prev, uuid] ), [] ); const { data: books, refetch } = useQuery<Book[]>({ queryFn: async () => { if ( locationState && "found" in locationState && !!locationState.found.length ) { return locationState.found; } const response = await fetch(`/api/books/list`); const data = await response.json(); return data; }, queryKey: ["books"], }); const shelf = params.get("shelf"); const year = params.get("year"); const data = useMemo( () => books?.filter( (b) => (!shelf || b.shelf?.toLowerCase() === shelf?.toLowerCase()) && (!year || b.published.toString() === year) ) ?? [], [books, year, shelf] ); const columnHelper = createColumnHelper<Book>(); const columns: ColumnDef<Book, any>[] = useMemo( () => [ authenticated ? columnHelper.display({ id: "selection", header: () => ( <Form.Check type='checkbox' checked={selectedBooks.length === data?.length} id={`select-all-books`} onChange={() => setSelectedBooks((prev) => prev.length || !data ? [] : data.map((b) => b.uuid) ) } /> ), cell: (props) => ( <Form.Check type='checkbox' checked={selectedBooks.includes(props.row.original.uuid)} id={`select-book-${props.row.original.uuid}`} onChange={() => toggleBookSelection(props.row.original.uuid)} /> ), }) : undefined, columnHelper.accessor((book) => book.isbn, { id: "isbn", header: "ISBN", size: 50, cell: (props) => <div>{props.row.original.isbn}</div>, }), columnHelper.accessor((book) => book.title, { id: "title", header: "Title", size: 200, cell: (props) => <div>{props.row.original.title}</div>, }), columnHelper.accessor((book) => book.published, { id: "published", header: "Year published", size: 30, cell: (props) => <div>{props.row.original.published}</div>, }), columnHelper.accessor((book) => book.shelf, { id: "shelf", header: "Shelf", size: 50, cell: (props) => <div>{props.row.original.shelf}</div>, }), columnHelper.accessor( (book) => (!book.checkoutBy ? "" : book.checkoutBy), { id: "checkoutBy", size: 50, header: "Checked out by", cell: (props) => ( <div> {!props.row.original.checkoutBy ? "" : props.row.original.checkoutBy} </div> ), } ), columnHelper.accessor((book) => book.lastCheckoutDate, { id: "lastCheckoutDate", header: "Last checkout", size: 50, cell: (props) => ( <div> {!props.row.original.lastCheckoutDate ? "" : format( Number(props.row.original.lastCheckoutDate), "yyyy MM dd" )} </div> ), }), authenticated ? columnHelper.accessor( (book) => (!book.contact ? "" : book.contact), { id: "contactInfo", header: "Contact info", size: 50, cell: (props) => ( <div> {!props.row.original.contact ? "" : props.row.original.contact} </div> ), } ) : undefined, columnHelper.display({ id: "actions", header: "Actions", size: 30, cell: (props) => ( <Actions book={props.row.original} refetch={refetch} authenticated={authenticated} setActiveModal={modalContext.setActiveModal} /> ), }), ].filter((c) => typeof c !== "undefined") as ColumnDef<Book, any>[], [ authenticated, columnHelper, selectedBooks, data, toggleBookSelection, refetch, modalContext.setActiveModal, ] ); const [pagination, setPagination] = React.useState<PaginationState>({ pageIndex: 0, pageSize: 10, }); const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), onPaginationChange: setPagination, state: { pagination, }, }); const currentPage = useMemo( () => table.getState().pagination.pageIndex, // eslint-disable-next-line react-hooks/exhaustive-deps [table.getState().pagination.pageIndex] ); return ( <div className='m-auto p-2' style={{ width: "100%", maxHeight: "100vh" }}> <ModalSelector {...modalContext} /> <h1>Library</h1> <div style={{ maxWidth: "100%", maxHeight: "80vh", overflow: "auto", marginBottom: "5px", }} > <Table striped bordered hover> <thead> {table.getHeaderGroups().map((headerGroup) => { return ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan}> <div style={{ cursor: "pointer" }} {...{ onClick: header.column.getToggleSortingHandler(), }} > {flexRender( header.column.columnDef.header, header.getContext() )} {!!header.column.getIsSorted() && ((header.column.getIsSorted() as string) === "asc" ? ( <ImArrowUp /> ) : ( <ImArrowDown /> ))} </div> </th> ))} </tr> ); })} </thead> <tbody> {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </Table> </div> {table.getPageCount() ? ( <div className='d-flex w-100'> <Pagination currentPage={currentPage} setPageIndex={table.setPageIndex} getPageCount={table.getPageCount} /> {authenticated && ( <Button style={{ marginLeft: "0px", marginRight: "auto", }} className='my-auto' onClick={() => modalContext.setActiveModal({ type: modalTypes.moveShelf, books: selectedBooks, onClose: () => { refetch(); modalContext.setActiveModal(); }, }) } > Move selection </Button> )} </div> ) : null} </div> ); };