librarian-web/src/app/catalogue/table.tsx

163 lines
4.1 KiB
TypeScript

import { getBooks } from "@/lib/api";
import { DetailedBook } from "@/lib/book";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeadCell,
TableRow,
} from "flowbite-react";
import { useState, useEffect, Dispatch, SetStateAction } from "react";
export function BookTable() {
const [books, setBooks] = useState([] as DetailedBook[]);
const [nameFilter, setNameFilter] = useState(null as string | null);
const [catFilter, setCatFilter] = useState([] as string[]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
getBooks().then((data) => {
if (!data) {
return setLoading(false);
}
setBooks(data);
setLoading(false);
});
}, []);
if (isLoading) return <p>Loading...</p>;
if (books.length <= 0) return <p>No library data</p>;
return (
<div className="w-full">
{renderOptions(setNameFilter, setCatFilter)}
<div className="overflow-x-auto">
{renderTable(books, nameFilter, catFilter)}
</div>
</div>
);
}
let searchNameTo: NodeJS.Timeout;
function renderOptions(
setNameFilter: Dispatch<SetStateAction<string | null>>,
setCatFilter: Dispatch<SetStateAction<string[]>>
) {
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setCatFilter; //TODO: implement category selector
clearTimeout(searchNameTo);
searchNameTo = setTimeout(() => {
setNameFilter(e.target.value || null);
}, 500);
};
return (
<div>
<div className="mx-auto max-w-md">
<label
htmlFor="default-search"
className="sr-only mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Search
</label>
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3">
<svg
className="h-4 w-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
</div>
<input
type="search"
id="default-search"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-4 ps-10 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="Search book titles or authors..."
onChange={(e) => handleNameChange(e)}
required
/>
</div>
</div>
</div>
);
}
function renderTable(
books: DetailedBook[],
nameFilter: string | null,
categoryFilter: string[]
) {
console.log("namef", nameFilter);
console.log("catf", categoryFilter);
if (nameFilter || categoryFilter.length > 0) {
books = books.filter((book) => {
let pass = true;
if (categoryFilter.length > 0) {
if (book.category.length <= 0) return false;
for (const category of book.category) {
pass = pass && categoryFilter.includes(category);
}
}
if (nameFilter) {
const namePresent =
book.title.toLowerCase().includes(nameFilter.toLowerCase()) ||
book.authorsList.findIndex((author) =>
author.toLowerCase().includes(nameFilter.toLowerCase())
) >= 0;
pass = pass && namePresent;
}
return pass;
});
}
let tableRows = books.map((book) => (
<TableRow key={book.id} className="hover:bg-zinc-400 hover:text-white">
<TableCell>{book.title}</TableCell>
<TableCell>{book.authors}</TableCell>
<TableCell>{book.category.join(", ")}</TableCell>
<TableCell>{book.isbn}</TableCell>
</TableRow>
));
if (tableRows.length <= 0) {
tableRows = [
<TableRow key="no-results">
<TableCell colSpan={4} className="text-center">
No results
</TableCell>
</TableRow>,
];
}
return (
<Table>
<TableHead className="bg-zinc-400">
<TableHeadCell className="w-2/5 ">Title</TableHeadCell>
<TableHeadCell>Authors</TableHeadCell>
<TableHeadCell>Category</TableHeadCell>
<TableHeadCell>ISBN</TableHeadCell>
</TableHead>
<TableBody>{tableRows}</TableBody>
</Table>
);
}