diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 06913c288..c6fbd2117 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -33713,6 +33713,27 @@ + + envelope_return_address + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + estimate false @@ -37619,6 +37640,27 @@ + + production_by_technician + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + purchases_by_cost_center_detail false diff --git a/client/package.json b/client/package.json index 80e88a5a2..34ae41cea 100644 --- a/client/package.json +++ b/client/package.json @@ -11,10 +11,10 @@ "@sentry/react": "^6.16.1", "@sentry/tracing": "^6.16.1", "@splitsoftware/splitio-react": "^1.3.0", - "@stripe/react-stripe-js": "^1.6.0", + "@stripe/react-stripe-js": "^1.7.0", "@stripe/stripe-js": "^1.22.0", "@tanem/react-nprogress": "^3.0.82", - "antd": "^4.17.3", + "antd": "^4.17.4", "apollo-link-logger": "^2.0.0", "axios": "^0.24.0", "craco-less": "^1.20.0", @@ -24,10 +24,10 @@ "env-cmd": "^10.1.0", "exifr": "^7.1.3", "firebase": "^9.6.1", - "graphql": "^16.1.0", - "i18next": "^21.6.0", + "graphql": "^16.2.0", + "i18next": "^21.6.3", "i18next-browser-languagedetector": "^6.1.2", - "jsoneditor": "^9.5.7", + "jsoneditor": "^9.5.8", "jsreport-browser-client-dist": "^1.3.0", "libphonenumber-js": "^1.9.44", "logrocket": "^2.1.2", @@ -40,7 +40,7 @@ "rc-queue-anim": "^2.0.0", "rc-scroll-anim": "^2.7.6", "react": "^17.0.2", - "react-big-calendar": "^0.38.1", + "react-big-calendar": "^0.38.2", "react-color": "^2.19.3", "react-cookie": "^4.1.1", "react-dom": "^17.0.2", @@ -49,7 +49,7 @@ "react-grid-layout": "^1.3.0", "react-i18next": "^11.15.1", "react-icons": "^4.3.1", - "react-number-format": "^4.8.0", + "react-number-format": "^4.9.0", "react-redux": "^7.2.6", "react-resizable": "^3.0.4", "react-router-dom": "^5.3.0", @@ -78,7 +78,8 @@ "workbox-range-requests": "^6.4.2", "workbox-routing": "^6.4.2", "workbox-strategies": "^6.4.2", - "workbox-streams": "^6.4.2" + "workbox-streams": "^6.4.2", + "yauzl": "^2.10.0" }, "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index fdcff6b90..4ad5ac0db 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -13,6 +13,7 @@ export default function ChatArchiveButton({ conversation }) { await updateConversation({ variables: { id: conversation.id, archived: !conversation.archived }, + refetchQueries: ["CONVERSATION_LIST_QUERY"], }); setLoading(false); diff --git a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx index 00dfa304f..fc3971a82 100644 --- a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx +++ b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx @@ -16,6 +16,16 @@ export default function ChatConversationTitleTags({ jobConversations }) { conversationId: convId, jobId: jobId, }, + update(cache) { + cache.modify({ + id: cache.identify({ id: convId, __typename: "conversations" }), + fields: { + job_conversations(ex) { + return ex.filter((e) => e.jobid !== jobId); + }, + }, + }); + }, }); logImEXEvent("messaging_remove_job_tag", { conversationId: convId, diff --git a/client/src/components/global-search/global-search.component.jsx b/client/src/components/global-search/global-search.component.jsx index 4135f8dc7..018aefdec 100644 --- a/client/src/components/global-search/global-search.component.jsx +++ b/client/src/components/global-search/global-search.component.jsx @@ -1,5 +1,5 @@ import { useLazyQuery } from "@apollo/client"; -import { AutoComplete, Divider, Input, Space } from "antd"; +import { AutoComplete, Divider, Space } from "antd"; import _ from "lodash"; import React from "react"; import { useTranslation } from "react-i18next"; @@ -11,8 +11,7 @@ import AlertComponent from "../alert/alert.component"; export default function GlobalSearch() { const { t } = useTranslation(); - const [callSearch, { loading, error, data }] = - useLazyQuery(GLOBAL_SEARCH_QUERY); + const [callSearch, { error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY); const executeSearch = (v) => { if (v && v.variables.search && v.variables.search !== "") callSearch(v); @@ -172,8 +171,7 @@ export default function GlobalSearch() { options={options} onSearch={handleSearch} placeholder={t("general.labels.globalsearch")} - > - - + allowClear + > ); } diff --git a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx index a1be3e834..957fe3344 100644 --- a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx @@ -5,45 +5,124 @@ import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; import cleanAxios from "../../utils/CleanAxios"; import formatBytes from "../../utils/formatbytes"; +import yauzl from "yauzl"; +import { useTreatments } from "@splitsoftware/splitio-react"; -export default function JobsDocumentsDownloadButton({ +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(JobsDocumentsDownloadButton); + +export function JobsDocumentsDownloadButton({ + bodyshop, galleryImages, identifier, }) { const { t } = useTranslation(); const [download, setDownload] = useState(null); + const { Direct_Media_Download } = useTreatments( + ["Direct_Media_Download"], + {}, + bodyshop.imexshopid + ); const imagesToDownload = [ ...galleryImages.images.filter((image) => image.isSelected), // ...galleryImages.other.filter((image) => image.isSelected), ]; - const handleDownload = () => { - logImEXEvent("jobs_documents_download"); - axios - .post("/media/download", { - ids: imagesToDownload.map((_) => _.key), - }) - .then((r) => { - // window.open(r.data); - downloadAs( - r.data, - `${identifier || "documents"}.zip`, - (progressEvent) => { - setDownload((currentDownloadState) => { - return { - downloaded: progressEvent.loaded || 0, - speed: - (progressEvent.loaded || 0) - - ((currentDownloadState && currentDownloadState.downloaded) || - 0), - }; - }); - }, - () => setDownload(null) - ); - }); - }; + function downloadProgress(progressEvent) { + setDownload((currentDownloadState) => { + return { + downloaded: progressEvent.loaded || 0, + speed: + (progressEvent.loaded || 0) - + ((currentDownloadState && currentDownloadState.downloaded) || 0), + }; + }); + } + const handleDownload = async () => { + logImEXEvent("jobs_documents_download"); + + const zipUrl = await axios({ + url: "/media/download", + method: "POST", + //responseType: "arraybuffer", // Important + data: { ids: imagesToDownload.map((_) => _.key) }, + }); + + const theDownloadedZip = await cleanAxios({ + url: zipUrl.data, + method: "GET", + responseType: "arraybuffer", + onDownloadProgress: downloadProgress, + }); + setDownload(null); + if (Direct_Media_Download.treatment === "on") { + try { + const parentDir = await window.showDirectoryPicker({ + id: "media", + startIn: "downloads", + }); + + const directory = await parentDir.getDirectoryHandle(identifier, { + create: true, + }); + + yauzl.fromBuffer( + Buffer.from(theDownloadedZip.data), + {}, + (err, zipFile) => { + if (err) throw err; + zipFile.on("entry", (entry) => { + zipFile.openReadStream(entry, async (readErr, readStream) => { + if (readErr) { + zipFile.close(); + throw readErr; + } + if (err) throw err; + let fileSystemHandle = await directory.getFileHandle( + entry.fileName, + { + create: true, + } + ); + const writable = await fileSystemHandle.createWritable(); + readStream.on("data", async function (chunk) { + await writable.write(chunk); + }); + readStream.on("end", async function () { + await writable.close(); + }); + }); + }); + } + ); + } catch (e) { + console.log(e); + standardMediaDownload(theDownloadedZip.data); + } + } else { + standardMediaDownload(theDownloadedZip.data); + } + + function standardMediaDownload(bufferData) { + const a = document.createElement("a"); + const url = window.URL.createObjectURL(new Blob([bufferData])); + a.href = url; + a.download = `${identifier || "documents"}.zip`; + a.click(); + } + }; return ( <>