From 666931aacd7017314278a76d6d0f7d0a99ecc2f4 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 23 Feb 2021 10:34:31 -0800 Subject: [PATCH] Resolved bulk printing & status for printing items. IO-690 --- .../print-center-item.component.jsx | 7 +- .../print-center-speed-print.component.jsx | 34 ++- .../shop-template-test-render.component.jsx | 2 +- ...me-tickets-summary-employees.component.jsx | 21 +- client/src/utils/RenderTemplate.js | 242 ++++++++++++------ client/src/utils/TemplateConstants.js | 11 +- 6 files changed, 197 insertions(+), 120 deletions(-) diff --git a/client/src/components/print-center-item/print-center-item.component.jsx b/client/src/components/print-center-item/print-center-item.component.jsx index 3016e1373..ed369f17f 100644 --- a/client/src/components/print-center-item/print-center-item.component.jsx +++ b/client/src/components/print-center-item/print-center-item.component.jsx @@ -1,5 +1,6 @@ import { MailOutlined, PrinterOutlined } from "@ant-design/icons"; -import React from "react"; +import { Spin } from "antd"; +import React, { useState } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { setEmailOptions } from "../../redux/email/email.actions"; @@ -22,8 +23,10 @@ export function PrintCenterItemComponent({ bodyshop, disabled, }) { + const [loading, setLoading] = useState(false); const { context } = printCenterModal; const renderToNewWindow = async () => { + setLoading(true); await GenerateDocument( { name: item.key, @@ -32,6 +35,7 @@ export function PrintCenterItemComponent({ {}, "p" ); + setLoading(false); }; if (disabled) return
  • {item.title}
  • ; @@ -51,6 +55,7 @@ export function PrintCenterItemComponent({ ); }} /> + {loading && } ); } diff --git a/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx b/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx index fa05515d3..090faee38 100644 --- a/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx +++ b/client/src/components/print-center-speed-print/print-center-speed-print.component.jsx @@ -1,11 +1,11 @@ import { Button, List, Typography } from "antd"; -import React from "react"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import { GenerateDocument } from "../../utils/RenderTemplate"; +import { GenerateDocuments } from "../../utils/RenderTemplate"; import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ @@ -16,26 +16,19 @@ const mapDispatchToProps = (dispatch) => ({ }); export function PrintCenterSpeedPrint({ bodyshop, jobId }) { + const [loading, setLoading] = useState(false); const { speedprint } = bodyshop; const { t } = useTranslation(); - const renderTemplate = async (templateKey) => { - logImEXEvent("speed_print_template_render"); - - GenerateDocument( - { - name: templateKey, - variables: { id: jobId }, - }, - {}, - "p" - ); - }; - - const renderAllTemplates = (templateKeys) => { + const renderAllTemplates = async (templateKeys) => { logImEXEvent("speed_print_render_all_templates"); - - templateKeys.forEach((templateKey) => renderTemplate(templateKey)); + setLoading(true); + await GenerateDocuments( + templateKeys.map((key) => { + return { name: key, variables: { id: jobId } }; + }) + ); + setLoading(false); }; return ( @@ -50,7 +43,10 @@ export function PrintCenterSpeedPrint({ bodyshop, jobId }) { renderItem={(sp) => ( renderAllTemplates(sp.templates)}> + , ]} diff --git a/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx b/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx index d27885e15..d2b83c865 100644 --- a/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx +++ b/client/src/components/shop-template-test-render/shop-template-test-render.component.jsx @@ -50,7 +50,7 @@ export function ShopTemplateTestRender({ view: inlineHtml.data, context: { ...contextData, bodyshop: bodyshop }, }); - displayTemplateInWindowNoprint(renderResponse.data); + // displayTemplateInWindowNoprint(renderResponse.data); setLoading(false); } catch (error) { diff --git a/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx b/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx index e6322f862..3f3c59955 100644 --- a/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx +++ b/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx @@ -1,16 +1,14 @@ -import React from "react"; -import { Statistic, Space, List, Button, Typography } from "antd"; -import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; -import { useTranslation } from "react-i18next"; +import { Button, List, Space, Statistic, Typography } from "antd"; import moment from "moment"; -import RenderTemplate, { - displayTemplateInWindow, -} from "../../utils/RenderTemplate"; -import { TemplateList } from "../../utils/TemplateConstants"; +import React from "react"; +import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { onlyUnique } from "../../utils/arrayHelper"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; +import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -68,15 +66,14 @@ export function TimeTicketsSummaryEmployees({ }); const handlePrintEmployeeTicket = async (empId) => { - alert("Missing Key!"); - const html = await RenderTemplate( + GenerateDocument( { name: TemplateList().time_tickets_by_employee.key, variables: { id: empId, start: startDate, end: endDate }, }, - bodyshop + {}, + "p" ); - displayTemplateInWindow(html); }; return ( diff --git a/client/src/utils/RenderTemplate.js b/client/src/utils/RenderTemplate.js index d0db5e0d0..4d6502db1 100644 --- a/client/src/utils/RenderTemplate.js +++ b/client/src/utils/RenderTemplate.js @@ -1,3 +1,4 @@ +import { notification } from "antd"; import axios from "axios"; import gql from "graphql-tag"; import jsreport from "jsreport-browser-client-dist"; @@ -5,16 +6,149 @@ import { auth } from "../firebase/firebase.utils"; import { setEmailOptions } from "../redux/email/email.actions"; import { store } from "../redux/store"; import client from "../utils/GraphQLClient"; +import { TemplateList } from "./TemplateConstants"; + const server = process.env.REACT_APP_REPORTS_SERVER_URL; jsreport.serverUrl = server; +const Templates = TemplateList(); + export default async function RenderTemplate( templateObject, bodyshop, renderAsHtml = false ) { //Query assets that match the template name. Must be in format <>.query + let { contextData, useShopSpecificTemplate } = await fetchContextData( + templateObject + ); + let reportRequest = { + template: { + name: useShopSpecificTemplate + ? `/${bodyshop.imexshopid}/${templateObject.name}` + : `/${templateObject.name}`, + ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }), + }, + data: { + ...contextData, + ...templateObject.variables, + ...templateObject.context, + headerpath: `/${bodyshop.imexshopid}/header.html`, + bodyshop: bodyshop, + }, + }; + try { + const render = await jsreport.renderAsync(reportRequest); + if (!renderAsHtml) { + render.download(Templates[templateObject.name].title || ""); + } else { + return new Promise((resolve, reject) => { + resolve(render.toString()); + }); + } + } catch (error) { + notification["error"]({ message: JSON.stringify(error) }); + } +} + +export async function RenderTemplates( + templateObjects, + bodyshop, + renderAsHtml = false +) { + //Query assets that match the template name. Must be in format <>.query + let templateAndData = []; + let proms = []; + templateObjects.forEach((template) => { + proms.push( + (async () => { + let { contextData, useShopSpecificTemplate } = await fetchContextData( + template + ); + templateAndData.push({ + templateObject: template, + contextData, + useShopSpecificTemplate, + }); + })() + ); + }); + await Promise.all(proms); + let rootTemplate = templateAndData.shift(); + + let reportRequest = { + template: { + name: rootTemplate.useShopSpecificTemplate + ? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}` + : `/${rootTemplate.templateObject.name}`, + ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }), + pdfOperations: templateAndData.map((template) => { + return { + template: { + name: template.useShopSpecificTemplate + ? `/${bodyshop.imexshopid}/${template.templateObject.name}` + : `/${template.templateObject.name}`, + ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }), + }, + data: { + ...template.contextData, + ...template.templateObject.variables, + ...template.templateObject.context, + headerpath: `/${bodyshop.imexshopid}/header.html`, + bodyshop: bodyshop, + }, + type: "append", + mergeWholeDocument: true, + renderForEveryPage: true, + }; + }), + }, + data: { + ...rootTemplate.contextData, + ...rootTemplate.templateObject.variables, + ...rootTemplate.templateObject.context, + headerpath: `/${bodyshop.imexshopid}/header.html`, + bodyshop: bodyshop, + }, + }; + + console.log("reportRequest", reportRequest); + + try { + const render = await jsreport.renderAsync(reportRequest); + if (!renderAsHtml) { + render.download("Speed Print"); + } else { + return new Promise((resolve, reject) => { + resolve(render.toString()); + }); + } + } catch (error) { + notification["error"]({ message: JSON.stringify(error) }); + } +} + +export const GenerateDocument = async (template, messageOptions, sendType) => { + const bodyshop = store.getState().user.bodyshop; + if (sendType === "e") { + store.dispatch( + setEmailOptions({ + messageOptions, + template, + }) + ); + } else { + await RenderTemplate(template, bodyshop); + } +}; + +export const GenerateDocuments = async (templates) => { + const bodyshop = store.getState().user.bodyshop; + await RenderTemplates(templates, bodyshop); +}; + +const fetchContextData = async (templateObject) => { jsreport.headers["Authorization"] = "Bearer " + (await auth.currentUser.getIdToken()); @@ -44,7 +178,7 @@ export default async function RenderTemplate( "There are too many queries to choose from. Please ensure there are no conflicting keys." ); } - let contextData; + let contextData = {}; if (templateQueryToExecute) { const { data } = await client.query({ query: gql(templateQueryToExecute), @@ -54,83 +188,37 @@ export default async function RenderTemplate( contextData = data; } - let reportRequest = { - template: { - name: useShopSpecificTemplate - ? `/${bodyshop.imexshopid}/${templateObject.name}` - : `/${templateObject.name}`, - ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }), - }, - data: { - ...(templateQueryToExecute ? contextData : {}), - ...templateObject.variables, - ...templateObject.context, - headerpath: `/${bodyshop.imexshopid}/header.html`, - bodyshop: bodyshop, - }, - }; - const render = await jsreport.renderAsync(reportRequest); - - if (!renderAsHtml) { - render.download(); - // var html = - // "" + - // "" + - // "" + - // '' + - // ""; - // displayTemplateInWindowNoprint(html); - } else { - return new Promise((resolve, reject) => { - resolve(render.toString()); - }); - } -} - -export const displayTemplateInWindow = (html) => { - try { - var newWin = window.open("", "_blank", "toolbar=0,location=0,menubar=0"); - newWin.document.write(html); - - setTimeout(function () { - newWin.document.close(); - newWin.focus(); - newWin.print(); - newWin.close(); - }, 500); - } catch (error) { - console.log("Unable to write to new window.", error); - } + return { contextData, useShopSpecificTemplate }; }; -export const displayTemplateInWindowNoprint = (html) => { - try { - var newWin = window.open("", "_blank", "toolbar=0,location=0,menubar=0"); - newWin.document.write(html); +//export const displayTemplateInWindow = (html) => { +// try { +// var newWin = window.open("", "_blank", "toolbar=0,location=0,menubar=0"); +// newWin.document.write(html); - setTimeout(function () { - newWin.document.close(); - newWin.focus(); - //newWin.print(); - //newWin.close(); - }, 500); - } catch (error) { - console.log("Unable to write to new window.", error); - } -}; +// setTimeout(function () { +// newWin.document.close(); +// newWin.focus(); +// newWin.print(); +// newWin.close(); +// }, 500); +// } catch (error) { +// console.log("Unable to write to new window.", error); +// } +// }; -export const GenerateDocument = async (template, messageOptions, sendType) => { - const bodyshop = store.getState().user.bodyshop; - if (sendType === "e") { - store.dispatch( - setEmailOptions({ - messageOptions, - template, - }) - ); - } else { - await RenderTemplate(template, bodyshop); - } -}; +// export const displayTemplateInWindowNoprint = (html) => { +// try { +// var newWin = window.open("", "_blank", "toolbar=0,location=0,menubar=0"); +// newWin.document.write(html); + +// setTimeout(function () { +// newWin.document.close(); +// newWin.focus(); +// //newWin.print(); +// //newWin.close(); +// }, 500); +// } catch (error) { +// console.log("Unable to write to new window.", error); +// } +// }; diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 129161477..61c23a049 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -12,15 +12,6 @@ export const TemplateList = (type, context) => { //If there's no type or the type is job, send it back. ...(!type || type === "job" ? { - estimate_detail: { - title: i18n.t("printcenter.jobs.estimate_detail"), - description: "Est Detail", - subject: `${i18n.t("printcenter.jobs.estimate_detail")} - ${ - context && context.job && context.job.ro_number - }`, - key: "estimate_detail", - disabled: false, - }, casl_authorization: { title: i18n.t("printcenter.jobs.casl_authorization"), description: "CASL Authorization", @@ -209,7 +200,7 @@ export const TemplateList = (type, context) => { title: i18n.t("printcenter.jobs.parts_order"), description: "Parts Order", key: "parts_order", - subject: `${bodyshop.shopname} Parts Order ${ + subject: `${bodyshop && bodyshop.shopname} Parts Order ${ (context && context && context.job &&