merge-move-all-books-from-shelf #14
9 changed files with 1942 additions and 422 deletions
2251
frontend/package-lock.json
generated
2251
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"fast-xml-parser": "^4.5.0",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-bootstrap": "^2.10.4",
|
"react-bootstrap": "^2.10.4",
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { Badge, Dropdown } from "react-bootstrap";
|
|
||||||
import { Book } from "../../../types/Book";
|
|
||||||
import { ImBin, ImBook, ImBoxAdd, ImBoxRemove, ImMenu } from "react-icons/im";
|
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import * as colors from "../../../colors";
|
import { useCallback } from "react";
|
||||||
import * as dark from "../../../darkmodeColors";
|
import { Badge, Dropdown } from "react-bootstrap";
|
||||||
|
import { ImBin, ImBook, ImBoxAdd, ImBoxRemove, ImMenu } from "react-icons/im";
|
||||||
import { ModalContextType } from "../../../App";
|
import { ModalContextType } from "../../../App";
|
||||||
import { modalTypes } from "../../../shared/components/modals/types";
|
import { modalTypes } from "../../../shared/components/modals/types";
|
||||||
import { useCallback } from "react";
|
import { Book } from "../../../types/Book";
|
||||||
|
|
||||||
const { book: bookModal, checkout, del } = modalTypes;
|
const { book: bookModal, checkout, del } = modalTypes;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { XMLParser } from "fast-xml-parser";
|
||||||
|
import { Book } from "../../../types/Book";
|
||||||
|
|
||||||
|
export const tryDeutscheNationalBibliothekApi = async (
|
||||||
|
isbn: string
|
||||||
|
): Promise<Pick<Book, "published" | "title"> | undefined> => {
|
||||||
|
const parser = new XMLParser();
|
||||||
|
|
||||||
|
const foundBooks = parser.parse(
|
||||||
|
await (
|
||||||
|
await fetch(
|
||||||
|
"https://services.dnb.de/sru/dnb?version=1.1&operation=searchRetrieve&query=" +
|
||||||
|
isbn
|
||||||
|
)
|
||||||
|
).text()
|
||||||
|
);
|
||||||
|
const foundBook =
|
||||||
|
foundBooks.searchRetrieveResponse?.records?.record?.recordData?.[
|
||||||
|
"rdf:RDF"
|
||||||
|
]?.["rdf:Description"];
|
||||||
|
|
||||||
|
if (!!foundBook) {
|
||||||
|
return {
|
||||||
|
published: foundBook["dcterms:issued"],
|
||||||
|
title: foundBook["dc:title"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
16
frontend/src/pages/Main/utils/tryGoogleBooksApi.ts
Normal file
16
frontend/src/pages/Main/utils/tryGoogleBooksApi.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { Book } from "../../../types/Book";
|
||||||
|
|
||||||
|
export const tryGoogleBooksApi = async (
|
||||||
|
isbn: string
|
||||||
|
): Promise<Pick<Book, "published" | "title"> | undefined> => {
|
||||||
|
const googleBooks = await (
|
||||||
|
await fetch("https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn)
|
||||||
|
).json();
|
||||||
|
if ("items" in googleBooks) {
|
||||||
|
return {
|
||||||
|
published: googleBooks.items[0].volumeInfo.publishedDate.substring(0, 4),
|
||||||
|
title: googleBooks.items[0].volumeInfo.title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
|
@ -6,6 +6,8 @@ import { useForm, useWatch } from "react-hook-form";
|
||||||
import { Book, bookShelfs } from "../../../types/Book";
|
import { Book, bookShelfs } from "../../../types/Book";
|
||||||
import { useScanner } from "../../utils/useScanner";
|
import { useScanner } from "../../utils/useScanner";
|
||||||
import { BookModalProps } from "./types";
|
import { BookModalProps } from "./types";
|
||||||
|
import { tryGoogleBooksApi } from "../../../pages/Main/utils/tryGoogleBooksApi";
|
||||||
|
import { tryDeutscheNationalBibliothekApi } from "../../../pages/Main/utils/tryDeutscheNationalBibliothekApi";
|
||||||
|
|
||||||
type BookForm = Pick<Book, "isbn" | "title" | "shelf" | "published">;
|
type BookForm = Pick<Book, "isbn" | "title" | "shelf" | "published">;
|
||||||
|
|
||||||
|
@ -27,24 +29,33 @@ export const BookModal = ({
|
||||||
reset(book);
|
reset(book);
|
||||||
}, [book, reset]);
|
}, [book, reset]);
|
||||||
|
|
||||||
const { scannerError, setScannerRef } = useScanner({
|
const [processingDetection, setProcessingDetection] = useState(false);
|
||||||
onDetected: async (result) => {
|
|
||||||
const googleBooks = await (
|
const onDetected = useCallback(
|
||||||
await fetch(
|
async (result: string) => {
|
||||||
"https://www.googleapis.com/books/v1/volumes?q=isbn:" + result
|
if (!processingDetection) {
|
||||||
)
|
setProcessingDetection(true);
|
||||||
).json();
|
const apiResponses = (
|
||||||
if ("items" in googleBooks) {
|
await Promise.all([
|
||||||
console.log(googleBooks);
|
tryDeutscheNationalBibliothekApi(result),
|
||||||
setValue(
|
tryGoogleBooksApi(result),
|
||||||
"published",
|
])
|
||||||
googleBooks.items[0].volumeInfo.publishedDate.substring(0, 4)
|
).filter((b) => !!b) as Pick<Book, "published" | "title">[];
|
||||||
);
|
if (apiResponses.length) {
|
||||||
setValue("title", googleBooks.items[0].volumeInfo.title);
|
Object.entries(apiResponses[0]).forEach(([key, value]) => {
|
||||||
|
setValue(key as keyof BookForm, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setValue("isbn", result);
|
||||||
|
setShowScanner(false);
|
||||||
|
setProcessingDetection(false);
|
||||||
}
|
}
|
||||||
setValue("isbn", result);
|
|
||||||
setShowScanner(false);
|
|
||||||
},
|
},
|
||||||
|
[processingDetection, setValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { scannerError, setScannerRef } = useScanner({
|
||||||
|
onDetected,
|
||||||
});
|
});
|
||||||
|
|
||||||
const values = useWatch({ control });
|
const values = useWatch({ control });
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { Alert, Button, Col, Form, Modal, Row } from "react-bootstrap";
|
import { Alert, Button, Col, Form, Modal, Row } from "react-bootstrap";
|
||||||
import { ImBoxAdd, ImBoxRemove } from "react-icons/im";
|
|
||||||
import { useForm, useWatch } from "react-hook-form";
|
import { useForm, useWatch } from "react-hook-form";
|
||||||
|
import { AiOutlineExclamationCircle } from "react-icons/ai";
|
||||||
|
import { ImBoxAdd, ImBoxRemove } from "react-icons/im";
|
||||||
import { Book } from "../../../types/Book";
|
import { Book } from "../../../types/Book";
|
||||||
import { primary, secondary } from "../../../colors";
|
|
||||||
import { ModalHeader } from "./ModalHeader";
|
import { ModalHeader } from "./ModalHeader";
|
||||||
import { CheckoutBookModalProps } from "./types";
|
import { CheckoutBookModalProps } from "./types";
|
||||||
import { AiOutlineExclamationCircle } from "react-icons/ai";
|
|
||||||
|
|
||||||
type BookCheckoutForm = Pick<Book, "checkoutBy" | "contact">;
|
type BookCheckoutForm = Pick<Book, "checkoutBy" | "contact">;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Button, Modal } from "react-bootstrap";
|
|
||||||
import { primary, secondary } from "../../../colors";
|
|
||||||
import { ModalHeader } from "./ModalHeader";
|
|
||||||
import { ImBin } from "react-icons/im";
|
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import { Button, Modal } from "react-bootstrap";
|
||||||
|
import { ImBin } from "react-icons/im";
|
||||||
|
import { ModalHeader } from "./ModalHeader";
|
||||||
import { DeleteBookModalProps } from "./types";
|
import { DeleteBookModalProps } from "./types";
|
||||||
|
|
||||||
export const DeleteBookModal = ({
|
export const DeleteBookModal = ({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext } from "react";
|
import React from "react";
|
||||||
import { Modal } from "react-bootstrap";
|
import { Modal } from "react-bootstrap";
|
||||||
import { ImCamera } from "react-icons/im";
|
import { ImCamera } from "react-icons/im";
|
||||||
import { useScanner } from "../../utils/useScanner";
|
import { useScanner } from "../../utils/useScanner";
|
||||||
|
|
Loading…
Reference in a new issue