From e2938594d2af5d7c8a8f3a6aa96628c11ae09fdd Mon Sep 17 00:00:00 2001 From: David Arranz Date: Mon, 19 Aug 2024 20:55:36 +0200 Subject: [PATCH] . --- .../app/quotes/components/QuotePDFPreview.tsx | 69 ++++---- .../app/quotes/components/QuotesDataTable.tsx | 2 +- .../LoadingSpinner/LoadingSpinner.tsx | 18 ++ client/src/components/LoadingSpinner/index.ts | 1 + client/src/components/PDFViewer/PDFViewer.tsx | 165 ++++++++---------- client/src/components/index.ts | 2 +- client/src/locales/en.json | 11 +- client/src/locales/es.json | 10 +- 8 files changed, 147 insertions(+), 131 deletions(-) create mode 100644 client/src/components/LoadingSpinner/LoadingSpinner.tsx create mode 100644 client/src/components/LoadingSpinner/index.ts diff --git a/client/src/app/quotes/components/QuotePDFPreview.tsx b/client/src/app/quotes/components/QuotePDFPreview.tsx index b6eccb7..383e150 100644 --- a/client/src/app/quotes/components/QuotePDFPreview.tsx +++ b/client/src/app/quotes/components/QuotePDFPreview.tsx @@ -16,7 +16,8 @@ import { } from "@/ui"; import { IListQuotes_Response_DTO } from "@shared/contexts"; import { t } from "i18next"; -import { CopyIcon, MoreVerticalIcon, TruckIcon } from "lucide-react"; +import { DownloadIcon, MoreVerticalIcon, PrinterIcon } from "lucide-react"; +import printJS from "print-js"; import { useMemo } from "react"; import { Trans } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -54,17 +55,17 @@ export const QuotePDFPreview = ({ if (reportIsLoading || reportIsPending || reportIsFetching) { return ( - - - - + + + + - - + + ); @@ -74,20 +75,33 @@ export const QuotePDFPreview = ({ - {t("quotes.list.quote")} - + {t("quotes.list.preview.quote")}
+ @@ -108,11 +122,11 @@ export const QuotePDFPreview = ({ - + - + @@ -123,19 +137,8 @@ export const QuotePDFPreview = ({ {quote?.date.toString()} - - - + + ); diff --git a/client/src/app/quotes/components/QuotesDataTable.tsx b/client/src/app/quotes/components/QuotesDataTable.tsx index 8f4e133..8cfae73 100644 --- a/client/src/app/quotes/components/QuotesDataTable.tsx +++ b/client/src/app/quotes/components/QuotesDataTable.tsx @@ -169,7 +169,7 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => { - +
); }; diff --git a/client/src/components/LoadingSpinner/LoadingSpinner.tsx b/client/src/components/LoadingSpinner/LoadingSpinner.tsx new file mode 100644 index 0000000..eab5e17 --- /dev/null +++ b/client/src/components/LoadingSpinner/LoadingSpinner.tsx @@ -0,0 +1,18 @@ +import { cn } from "@/lib/utils"; +import { LoaderIcon } from "lucide-react"; +import * as React from "react"; + +const spinnerVariants = "w-6 h-6 rounded-full animate-spin"; + +interface LoadingSpinnerProps extends React.HTMLAttributes { + className?: string; +} + +const LoadingSpinner = React.forwardRef((props, ref) => { + const { className, ...rest } = props; + return ; +}); + +LoadingSpinner.displayName = "LoadingSpinner"; + +export { LoadingSpinner }; diff --git a/client/src/components/LoadingSpinner/index.ts b/client/src/components/LoadingSpinner/index.ts new file mode 100644 index 0000000..ba09efc --- /dev/null +++ b/client/src/components/LoadingSpinner/index.ts @@ -0,0 +1 @@ +export * from "./LoadingSpinner"; diff --git a/client/src/components/PDFViewer/PDFViewer.tsx b/client/src/components/PDFViewer/PDFViewer.tsx index 9ba3b9c..bf51ec4 100644 --- a/client/src/components/PDFViewer/PDFViewer.tsx +++ b/client/src/components/PDFViewer/PDFViewer.tsx @@ -1,9 +1,12 @@ +import { cn } from "@/lib/utils"; import { Button } from "@/ui"; import { useResizeObserver } from "@wojtekmaj/react-hooks"; -import type { PDFDocumentProxy } from "pdfjs-dist"; -import printJS from "print-js"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { t } from "i18next"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import { useCallback, useMemo, useState } from "react"; import { Document, Page, pdfjs } from "react-pdf"; +import { LoadingSpinner } from "../LoadingSpinner"; + import "react-pdf/dist/esm/Page/AnnotationLayer.css"; import "react-pdf/dist/esm/Page/TextLayer.css"; @@ -28,15 +31,9 @@ export interface PDFViewerProps { } export const PDFViewer = ({ file, className }: PDFViewerProps): JSX.Element => { - const [eventResize, setEventResize] = useState(false); - - const [numPages, setNumPages] = useState(0); - const [pageNumber, setPageNumber] = useState(1); - const [renderedPageNumber, setRenderedPageNumber] = useState(null); - - const [width, setWidth] = useState(undefined); - const parentRef = useRef(null); - const canvasRef = useRef(null); + const [numPages, setNumPages] = useState(0); + const [pageNumber, setPageNumber] = useState(1); + const [renderedPageNumber, setRenderedPageNumber] = useState(undefined); const [containerRef, setContainerRef] = useState(null); const [containerWidth, setContainerWidth] = useState(); @@ -51,38 +48,15 @@ export const PDFViewer = ({ file, className }: PDFViewerProps): JSX.Element => { useResizeObserver(containerRef, resizeObserverOptions, onResize); - const hidePageCanvas = useCallback(() => { - const canvas = containerRef?.current?.querySelector("canvas"); - if (canvas) canvas.style.visibility = "hidden"; - }, [containerRef]); - - const showPageCanvas = useCallback(() => { - const canvas = containerRef?.current?.querySelector("canvas"); - if (canvas) canvas.style.visibility = "visible"; - }, [containerRef]); - - const onPageLoadSuccess = useCallback(() => { - hidePageCanvas(); - }, [hidePageCanvas]); - const onPageRenderSuccess = useCallback(() => { - showPageCanvas(); - }, [showPageCanvas]); + setRenderedPageNumber(pageNumber); + }, [setRenderedPageNumber, pageNumber]); - const onPageRenderError = useCallback(() => { - showPageCanvas(); - }, [showPageCanvas]); - - function onDocumentLoadSuccess({ numPages: nextNumPages }: PDFDocumentProxy): void { + function onDocumentLoadSuccess({ numPages }: { numPages: number }) { setPageNumber(1); - setNumPages(nextNumPages); + setNumPages(numPages); } - /*function onDocumentLoadSuccess({ numPages: nextNumPages }: PDFDocumentProxy): void { - setPageNumber(1); - setNumPages(nextNumPages); - }*/ - const changePage = (offset: number) => setPageNumber((prevPage) => offset > 0 ? Math.min(prevPage + offset, numPages) : Math.max(prevPage + offset, 1) @@ -93,77 +67,82 @@ export const PDFViewer = ({ file, className }: PDFViewerProps): JSX.Element => { const goToFirstPage = () => setPageNumber(1); const goToLastPage = () => setPageNumber(numPages); - useEffect(() => { - if (numPages > 0 || eventResize) { - const parentWidth = parentRef.current ? parentRef.current.offsetWidth : 0; - const canvasWidth = canvasRef.current ? canvasRef.current.width : 0; - - console.log("Document => ", parentWidth); - console.log("Canvas => ", canvasWidth); - - setWidth(parentWidth); - setEventResize(false); - } - }, [eventResize, numPages]); - - //file={`data:application/pdf;base64,$(pdfBase64String)`} - - const isLoading = renderedPageNumber !== pageNumber; + const isLoading = useMemo( + () => renderedPageNumber !== pageNumber, + [renderedPageNumber, pageNumber] + ); return ( -
+
} + options={options} + className={`w-full aspect-[3/4] relative bg-white shadow w-[${ + containerWidth ? Math.min(containerWidth, maxWidth) : maxWidth + }]`} > - {isLoading && renderedPageNumber ? ( - - ) : null} + {/** + * IMPORTANT: Keys are necessary so that React will know which Page component + * instances to use. + * Without keys, on page number update, React would replace the page number + * in 1st and 2nd page components. This may cause previously rendered page + * to render again, thus causing a flash. + * With keys, React, will add prevPage className to previously rendered page, + * and mount new Page component instance for the new page. + */} + + + -

- Página {pageNumber} de {numPages} -

-
- -
+

+ {t("common.num_page_of_total", { + count: pageNumber, + total: numPages, + })} +

); }; diff --git a/client/src/components/index.ts b/client/src/components/index.ts index 09aeea2..604a37d 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -8,8 +8,8 @@ export * from "./EmptyState"; export * from "./ErrorOverlay"; export * from "./Forms"; export * from "./Layout"; -export * from "./LoadingIndicator"; export * from "./LoadingOverlay"; +export * from "./LoadingSpinner"; export * from "./PDFViewer"; export * from "./ProtectedRoute"; //export * from "./SorteableDataTable"; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 84efdd8..126eaa6 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -46,7 +46,11 @@ "pick_date": "Select a date", "required_field": "This field is required", "unsaved_changes_prompt": "There are unsaved changes. If you leave, you'll lose your changes.", - "edit": "Edit" + "edit": "Edit", + "remove": "Remove", + "archive": "Archive", + "duplicate": "Duplicate", + "print": "Print" }, "main_menu": { "home": "Home", @@ -111,7 +115,10 @@ "customer_information": "Customer", "total_price": "Imp. total" }, - "quote": "Quote" + "preview": { + "quote": "Quote", + "download_quote": "Download quote" + } }, "create": { "title": "New quote", diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 367b2e2..c5a4636 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -46,7 +46,11 @@ "pick_date": "Elige una fecha", "required_field": "Este campo es obligatorio", "unsaved_changes_prompt": "Los últimos cambios no se han guardado. Si continúas, se perderán.", - "edit": "Editar" + "edit": "Editar", + "remove": "Eliminar", + "archive": "Archivar", + "duplicate": "Duplicar", + "print": "Imprimir" }, "main_menu": { "home": "Inicio", @@ -110,6 +114,10 @@ "status": "Estado", "customer_information": "Cliente", "total_price": "Imp. total" + }, + "preview": { + "quote": "Cotización", + "download_quote": "Descargar" } }, "create": {