n39librarian/frontend/src/pages/Library/Library.tsx

315 lines
9.3 KiB
TypeScript

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>
);
};