merge-move-all-books-from-shelf #14

Merged
0Ry5 merged 4 commits from move-all-books-from-shelf into main 2024-10-11 13:31:46 +02:00
9 changed files with 1942 additions and 422 deletions
Showing only changes of commit 65e9aa2e0b - Show all commits

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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;

View file

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

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

View file

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

View file

@ -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">;

View file

@ -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 = ({

View file

@ -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";