move to git.n39.euall books in the bag ...
This commit is contained in:
commit
8cc2662092
64 changed files with 134252 additions and 0 deletions
frontend/src/shared/components/modals
268
frontend/src/shared/components/modals/BookModal.tsx
Normal file
268
frontend/src/shared/components/modals/BookModal.tsx
Normal file
|
@ -0,0 +1,268 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Button, Col, Form, Modal, Row } from "react-bootstrap";
|
||||
import { ImBook, ImCamera } from "react-icons/im";
|
||||
import { ModalHeader } from "./ModalHeader";
|
||||
import { useForm, useWatch } from "react-hook-form";
|
||||
import { Book, bookShelfs } from "../../../types/Book";
|
||||
import { useScanner } from "../../utils/useScanner";
|
||||
import { primary, primaryRGBA, secondary } from "../../../colors";
|
||||
import { BookModalProps } from "./types";
|
||||
|
||||
type BookForm = Pick<Book, "isbn" | "title" | "shelf" | "published">;
|
||||
|
||||
export const BookModal = ({
|
||||
open,
|
||||
onClose,
|
||||
book,
|
||||
}: Omit<BookModalProps, "type">): React.JSX.Element => {
|
||||
const isEdit = !!book;
|
||||
const [showScanner, setShowScanner] = useState(false);
|
||||
|
||||
const { control, register, formState, setError, setValue, reset } =
|
||||
useForm<BookForm>({
|
||||
mode: "onChange",
|
||||
defaultValues: book,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset(book);
|
||||
}, [book, reset]);
|
||||
|
||||
const { scannerError, setScannerRef } = useScanner({
|
||||
onDetected: (result) => {
|
||||
setValue("isbn", result);
|
||||
setShowScanner(false);
|
||||
},
|
||||
});
|
||||
|
||||
const values = useWatch({ control });
|
||||
|
||||
const [submitError, setSubmitError] = useState<string | undefined>();
|
||||
const onSubmit = useCallback(
|
||||
async (data: Partial<BookForm>) => {
|
||||
setSubmitError(undefined);
|
||||
let error = false;
|
||||
if (!data.isbn?.length) {
|
||||
setError("isbn", { message: "ISBN is required" });
|
||||
error = true;
|
||||
}
|
||||
if (!data.title?.length) {
|
||||
setError("title", { message: "Title is required" });
|
||||
error = true;
|
||||
}
|
||||
if (!data.shelf?.length) {
|
||||
setError("shelf", { message: "Shelf is required" });
|
||||
error = true;
|
||||
}
|
||||
if (!data.published) {
|
||||
setError("published", { message: "Year published is required" });
|
||||
error = true;
|
||||
}
|
||||
if (error) return;
|
||||
const createdBook = await fetch(
|
||||
isEdit ? "api/books/edit" : "/api/books/create",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!createdBook.ok) {
|
||||
setSubmitError(await createdBook.text());
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[isEdit, onClose, setError]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
show={open}
|
||||
onHide={onClose}
|
||||
style={{ backgroundColor: primaryRGBA }}
|
||||
centered
|
||||
>
|
||||
<ModalHeader
|
||||
onClose={onClose}
|
||||
title={`${!!book ? "Edit" : "Add new"} Book`}
|
||||
icon={<ImBook size={50} className="ml-0 mr-auto" />}
|
||||
/>
|
||||
<Modal.Body
|
||||
style={{
|
||||
border: "2px solid black",
|
||||
borderTop: "none",
|
||||
backgroundColor: primary,
|
||||
}}
|
||||
>
|
||||
{!showScanner && (
|
||||
<Form
|
||||
className="mb-2"
|
||||
onSubmit={(ev) => {
|
||||
ev.preventDefault();
|
||||
onSubmit(values);
|
||||
}}
|
||||
>
|
||||
<Form.Group as={Row} className="mb-2">
|
||||
<Col sm="2">
|
||||
<Form.Label>ISBN</Form.Label>
|
||||
</Col>
|
||||
<Col className="d-flex flex-column">
|
||||
<div className="d-flex">
|
||||
<Form.Control
|
||||
{...register("isbn", { required: true, maxLength: 360 })}
|
||||
isInvalid={!!formState.errors.isbn}
|
||||
style={{
|
||||
borderRadius: "5px 0px 0px 5px",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={{
|
||||
borderRadius: "0px 5px 5px 0px",
|
||||
backgroundColor: secondary,
|
||||
color: "black",
|
||||
border: "2px solid black",
|
||||
}}
|
||||
className="mr-2 pt-0"
|
||||
disabled={showScanner}
|
||||
onClick={() => setShowScanner(true)}
|
||||
>
|
||||
<ImCamera size={20} />
|
||||
</Button>
|
||||
</div>
|
||||
<Form.Control.Feedback
|
||||
style={{ display: !formState.errors.isbn ? "none" : "block" }}
|
||||
type="invalid"
|
||||
>
|
||||
{!formState.errors.isbn
|
||||
? "ISBN is required"
|
||||
: formState.errors.isbn?.message}
|
||||
</Form.Control.Feedback>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form.Group as={Row} className="mb-2">
|
||||
<Col sm="2">
|
||||
<Form.Label>Title</Form.Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Form.Control
|
||||
{...register("title", { required: true, maxLength: 360 })}
|
||||
isInvalid={!!formState.errors.title}
|
||||
/>
|
||||
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{!values.title
|
||||
? "Title is required"
|
||||
: formState.errors.title?.message}
|
||||
</Form.Control.Feedback>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form.Group as={Row} className="mb-2">
|
||||
<Col sm="2">
|
||||
<Form.Label>Year published</Form.Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Form.Control
|
||||
{...register("published", {
|
||||
required: true,
|
||||
maxLength: 360,
|
||||
validate: {
|
||||
isReasonable: (value) => {
|
||||
if (value > 1900 && value < 2150) {
|
||||
return true;
|
||||
}
|
||||
return `${value} is not a reasonable year`;
|
||||
},
|
||||
isNumber: (value) => {
|
||||
if (value && value.toString().match(/^[0-9\b]+$/)) {
|
||||
return true;
|
||||
}
|
||||
return "Please enter a valid year.";
|
||||
},
|
||||
},
|
||||
})}
|
||||
isInvalid={!!formState.errors.published}
|
||||
/>
|
||||
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{!values.published
|
||||
? "Year published is required"
|
||||
: formState.errors.published?.message}
|
||||
</Form.Control.Feedback>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form.Group as={Row} className="mb-2">
|
||||
<Col sm="2">
|
||||
<Form.Label>Shelf</Form.Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Form.Select
|
||||
{...register("shelf", { required: true })}
|
||||
isInvalid={!!formState.errors.shelf}
|
||||
>
|
||||
{Object.keys(bookShelfs).map((key) => (
|
||||
<option
|
||||
key="key"
|
||||
value={bookShelfs[key as keyof typeof bookShelfs]}
|
||||
>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{!values.shelf
|
||||
? "Shelf is required"
|
||||
: formState.errors.shelf?.message}
|
||||
</Form.Control.Feedback>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<div className="d-flex mx-auto mb-auto mt-2 w-100">
|
||||
<Button
|
||||
style={{
|
||||
borderRadius: "5px",
|
||||
backgroundColor: secondary,
|
||||
color: "black",
|
||||
border: "2px solid black",
|
||||
marginLeft: "auto",
|
||||
marginRight: "10px",
|
||||
}}
|
||||
onClick={() => onSubmit(values)}
|
||||
>
|
||||
{isEdit ? "Submit" : "Add Book"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
{showScanner && (
|
||||
<div
|
||||
className="w-100 overflow-hidden"
|
||||
ref={(ref) => setScannerRef(ref)}
|
||||
style={{ position: "relative", height: "25vh" }}
|
||||
>
|
||||
<canvas
|
||||
className="drawingBuffer w-100 position-absolute"
|
||||
style={{
|
||||
height: "100%",
|
||||
border: "2px solid black",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{scannerError ? (
|
||||
<p>
|
||||
ERROR INITIALIZING CAMERA ${JSON.stringify(scannerError)} -- DO YOU
|
||||
HAVE PERMISSION?
|
||||
</p>
|
||||
) : null}
|
||||
{!!submitError && (
|
||||
<Form.Control.Feedback style={{ display: "block" }} type="invalid">
|
||||
{submitError}
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue