diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 57326a1e9..b4512fbe2 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -11040,6 +11040,27 @@ + + uploading + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + usage false @@ -11087,6 +11108,27 @@ + + edituploaded + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + insert false diff --git a/client/src/components/document-editor/document-editor.component.jsx b/client/src/components/document-editor/document-editor.component.jsx index ba5748d32..cb12c1aec 100644 --- a/client/src/components/document-editor/document-editor.component.jsx +++ b/client/src/components/document-editor/document-editor.component.jsx @@ -1,8 +1,8 @@ //import "tui-image-editor/dist/tui-image-editor.css"; -import { Spin } from "antd"; +import { Result } from "antd"; import * as markerjs2 from "markerjs2"; -import React, { useEffect, useRef } from "react"; -import { useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { @@ -11,6 +11,7 @@ import { } from "../../redux/user/user.selectors"; import { handleUpload } from "../documents-upload/documents-upload.utility"; import { GenerateSrcUrl } from "../jobs-documents-gallery/job-documents.utility"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -23,37 +24,49 @@ const mapDispatchToProps = (dispatch) => ({ export function DocumentEditorComponent({ currentUser, bodyshop, document }) { const imgRef = useRef(null); const [loading, setLoading] = useState(false); + const [uploaded, setuploaded] = useState(false); const markerArea = useRef(null); - const triggerUpload = async (dataUrl) => { - setLoading(true); - handleUpload( - { - filename: `${document.key.split("/").pop()}-${Date.now()}.jpg`, - file: await b64toBlob(dataUrl), - onSuccess: () => setLoading(false), - onError: () => setLoading(false), - }, - { - bodyshop: bodyshop, - uploaded_by: currentUser.email, - jobId: document.jobid, - //billId: billId, - tagsArray: ["edited"], - //callback: callbackAfterUpload, - } - ); - }; + const { t } = useTranslation(); + + const triggerUpload = useCallback( + async (dataUrl) => { + setLoading(true); + handleUpload( + { + filename: `${document.key.split("/").pop()}-${Date.now()}.jpg`, + file: await b64toBlob(dataUrl), + onSuccess: () => { + setLoading(false); + setuploaded(true); + }, + onError: () => setLoading(false), + }, + { + bodyshop: bodyshop, + uploaded_by: currentUser.email, + jobId: document.jobid, + //billId: billId, + tagsArray: ["edited"], + //callback: callbackAfterUpload, + } + ); + }, + [bodyshop, currentUser, document] + ); useEffect(() => { if (imgRef.current !== null) { // create a marker.js MarkerArea markerArea.current = new markerjs2.MarkerArea(imgRef.current); + console.log(`markerArea.current`, markerArea.current); // attach an event handler to assign annotated image back to our image element markerArea.current.addCloseEventListener((closeEvent) => { console.log("Close Event", closeEvent); }); markerArea.current.addRenderEventListener((dataUrl) => { + imgRef.current.src = dataUrl; + markerArea.current.close(); triggerUpload(dataUrl); }); // launch marker.js @@ -65,7 +78,7 @@ export function DocumentEditorComponent({ currentUser, bodyshop, document }) { markerArea.current.show(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [imgRef.current, triggerUpload]); + }, [triggerUpload]); async function b64toBlob(url) { const res = await fetch(url); @@ -74,7 +87,7 @@ export function DocumentEditorComponent({ currentUser, bodyshop, document }) { return (
- + {!loading && !uploaded && ( - + )} + {loading && } + {uploaded && ( + + )}
); } diff --git a/client/src/components/document-editor/document-editor.container.jsx b/client/src/components/document-editor/document-editor.container.jsx index 7e19b98d4..3819f27f8 100644 --- a/client/src/components/document-editor/document-editor.container.jsx +++ b/client/src/components/document-editor/document-editor.container.jsx @@ -1,29 +1,55 @@ import { useQuery } from "@apollo/client"; -import { Modal, Result } from "antd"; +import { Result } from "antd"; import queryString from "query-string"; -import React from "react"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import { useLocation } from "react-router"; +import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; import { GET_DOCUMENT_BY_PK } from "../../graphql/documents.queries"; +import { setBodyshop } from "../../redux/user/user.actions"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import DocumentEditor from "./document-editor.component"; -import { useTranslation } from "react-i18next"; -export default function DocumentEditorContainer() { +const mapDispatchToProps = (dispatch) => ({ + setBodyshop: (bs) => dispatch(setBodyshop(bs)), +}); + +export default connect(null, mapDispatchToProps)(DocumentEditorContainer); + +export function DocumentEditorContainer({ setBodyshop }) { //Get the image details for the image to be saved. //Get the document id from the search string. const { documentId } = queryString.parse(useLocation().search); const { t } = useTranslation(); + const { + loading: loadingShop, + error: errorShop, + data: dataShop, + } = useQuery(QUERY_BODYSHOP, { + fetchPolicy: "network-only", + }); + + useEffect(() => { + if (dataShop) setBodyshop(dataShop.bodyshops[0]); + }, [dataShop, setBodyshop]); const { loading, error, data } = useQuery(GET_DOCUMENT_BY_PK, { variables: { documentId }, skip: !documentId, }); - if (loading) return ; - if (error) return ; + if (loading || loadingShop) return ; + if (error || errorShop) + return ( + + ); - if (!data.documents_by_pk) + if (!data || !data.documents_by_pk) return ; return (
diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index ed27f08cb..c975388f2 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -1,5 +1,5 @@ -import { FileExcelFilled } from "@ant-design/icons"; -import { Card, Col, Row, Space } from "antd"; +import { FileExcelFilled, EditFilled, SyncOutlined } from "@ant-design/icons"; +import { Card, Col, Row, Space, Button } from "antd"; import React, { useEffect, useState } from "react"; import Gallery from "react-grid-gallery"; import { useTranslation } from "react-i18next"; @@ -23,6 +23,25 @@ function JobsDocumentsComponent({ }) { const [galleryImages, setgalleryImages] = useState({ images: [], other: [] }); const { t } = useTranslation(); + const [index, setIndex] = useState(0); + + const onCurrentImageChange = (index) => { + setIndex(index); + }; + + useEffect(() => { + console.log("Added event listening for reteching."); + window.addEventListener("storage", (ev) => { + if (ev.key === "refetch" && ev.newValue === true) { + refetch && refetch(); + localStorage.setItem("refetch", false); + } + }); + + return () => { + window.removeEventListener("storage"); + }; + }, [refetch]); useEffect(() => { let documents = data.reduce( @@ -101,6 +120,9 @@ function JobsDocumentsComponent({ + { + console.log(`Clicked`); + const newWindow = window.open( + `${window.location.protocol}//${window.location.host}/edit?documentId=${galleryImages.images[index].id}`, + "_blank", + "noopener,noreferrer" + ); + if (newWindow) newWindow.opener = null; + }} + > + + , + ]} onClickImage={(props) => { window.open( props.target.src, diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e2ffadc03..c2f2d2791 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -6,7 +6,7 @@ }, "errors": { "deleting": "Error encountered while deleting allocation. {{message}}", - "saving": "Error while allocating. {{message}}", + "saving": "Error while allocating. {{message}}", "validation": "Please ensure all fields are entered correctly. " }, "fields": { @@ -701,10 +701,12 @@ "upload": "Upload", "upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ", "upload_limitexceeded_title": "Unable to upload document(s)", + "uploading": "Uploading...", "usage": "of job storage used. ({{used}} / {{total}})" }, "successes": { "delete": "Document(s) deleted successfully.", + "edituploaded": "Edited document uploaded successfully. Please close this window and refresh the documents list.", "insert": "Uploaded document successfully. ", "updated": "Document updated successfully. " } diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 606c03ce3..5cba0da9a 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -701,10 +701,12 @@ "upload": "Subir", "upload_limitexceeded": "", "upload_limitexceeded_title": "", + "uploading": "", "usage": "" }, "successes": { "delete": "Documento eliminado con éxito.", + "edituploaded": "", "insert": "Documento cargado con éxito.", "updated": "" } diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index f7ee47e50..c480fc6cb 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -701,10 +701,12 @@ "upload": "Télécharger", "upload_limitexceeded": "", "upload_limitexceeded_title": "", + "uploading": "", "usage": "" }, "successes": { "delete": "Le document a bien été supprimé.", + "edituploaded": "", "insert": "Document téléchargé avec succès.", "updated": "" }