BOD-34 Moved template rendering to utility class. Added basic config for some estimate reports.

This commit is contained in:
Patrick Fic
2020-04-28 16:41:36 -07:00
parent 113bf3f0fb
commit c6a29ba417
14 changed files with 396 additions and 133 deletions

View File

@@ -11390,7 +11390,133 @@
<name>jobs</name> <name>jobs</name>
<children> <children>
<concept_node> <concept_node>
<name>repairorder</name> <name>appointment_reminder</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>casl_work_authorization</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>coversheet</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>estimate_detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>fippa_work_authorization</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>job_totals</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>work_authorization</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
<description></description> <description></description>
<comment></comment> <comment></comment>
@@ -11415,6 +11541,48 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>misc</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>repairorder</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>title</name> <name>title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,21 +1,20 @@
import { useApolloClient } from "@apollo/react-hooks"; import { useApolloClient } from "@apollo/react-hooks";
import { Modal, notification } from "antd"; import { Modal, notification } from "antd";
import { gql } from "apollo-boost";
import axios from "axios"; import axios from "axios";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_TEMPLATES_BY_NAME } from "../../graphql/templates.queries"; import { EmailSettings } from "../../emails/constants";
import { toggleEmailOverlayVisible } from "../../redux/email/email.actions"; import { toggleEmailOverlayVisible } from "../../redux/email/email.actions";
import { import {
selectEmailConfig, selectEmailConfig,
selectEmailVisible, selectEmailVisible,
} from "../../redux/email/email.selectors.js"; } from "../../redux/email/email.selectors.js";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RenderTemplate from "../../utils/RenderTemplate";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import EmailOverlayComponent from "./email-overlay.component"; import EmailOverlayComponent from "./email-overlay.component";
import { EmailSettings } from "../../emails/constants";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
modalVisible: selectEmailVisible, modalVisible: selectEmailVisible,
@@ -32,6 +31,7 @@ export function EmailOverlayContainer({
bodyshop, bodyshop,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const defaultEmailFrom = { const defaultEmailFrom = {
from: { from: {
name: bodyshop.shopname || EmailSettings.fromNameDefault, name: bodyshop.shopname || EmailSettings.fromNameDefault,
@@ -41,58 +41,10 @@ export function EmailOverlayContainer({
}; };
const [messageOptions, setMessageOptions] = useState({ const [messageOptions, setMessageOptions] = useState({
...defaultEmailFrom, ...defaultEmailFrom,
html: "",
}); });
const client = useApolloClient(); const client = useApolloClient();
const renderEmail = () => {
client
.query({
query: QUERY_TEMPLATES_BY_NAME,
variables: { name: emailConfig.template.name },
fetchPolicy: "network-only",
})
.then(({ data: templateRecords }) => {
let templateToUse;
if (templateRecords.templates.length === 1) {
console.log("Only 1 Template found.");
templateToUse = templateRecords.templates[0];
} else if (templateRecords.templates.length === 2) {
console.log("2 Templates found..");
templateToUse = templateRecords.templates.filter(
(t) => !!t.bodyshopid
);
} else {
//No template found.Uh oh.
alert("Templating Error!");
}
client
.query({
query: gql(templateToUse.query),
variables: { ...emailConfig.template.variables },
fetchPolicy: "network-only",
})
.then(({ data: contextData }) => {
handleRender(contextData, templateToUse.html);
});
});
};
const handleRender = (contextData, html) => {
axios
.post("/render", {
view: html,
context: { ...contextData, bodyshop: bodyshop },
})
.then((r) => {
setMessageOptions({
...emailConfig.messageOptions,
...defaultEmailFrom,
html: r.data,
});
});
};
const handleOk = () => { const handleOk = () => {
//sendEmail(messageOptions); //sendEmail(messageOptions);
axios axios
@@ -118,8 +70,19 @@ export function EmailOverlayContainer({
setMessageOptions({ ...messageOptions, html: text }); setMessageOptions({ ...messageOptions, html: text });
}; };
const render = async () => {
setLoading(true);
let html = await RenderTemplate(emailConfig.template, client, bodyshop);
setMessageOptions({
...emailConfig.messageOptions,
...defaultEmailFrom,
html: html,
});
setLoading(false);
};
useEffect(() => { useEffect(() => {
if (modalVisible) renderEmail(); if (modalVisible) render();
}, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps }, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps
return ( return (
@@ -131,7 +94,7 @@ export function EmailOverlayContainer({
onCancel={() => { onCancel={() => {
toggleEmailOverlayVisible(); toggleEmailOverlayVisible();
}}> }}>
<LoadingSpinner loading={false}> <LoadingSpinner loading={loading}>
<EmailOverlayComponent <EmailOverlayComponent
handleConfigChange={handleConfigChange} handleConfigChange={handleConfigChange}
messageOptions={messageOptions} messageOptions={messageOptions}

View File

@@ -3,9 +3,13 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { selectPrintCenter } from "../../redux/modals/modals.selectors"; import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
import RenderTemplate from "../../utils/RenderTemplate";
import { useApolloClient } from "@apollo/react-hooks";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter, printCenterModal: selectPrintCenter,
bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
@@ -15,8 +19,46 @@ export function PrintCenterItemComponent({
printCenterModal, printCenterModal,
setEmailOptions, setEmailOptions,
item, item,
id,
bodyshop,
disabled,
}) { }) {
return <li>{item.title}</li>; const client = useApolloClient();
const renderToNewWindow = async () => {
const html = await RenderTemplate(
{
name: item.key,
variables: { id: id },
},
client,
bodyshop
);
var newWin = window.open();
newWin.document.write(html);
};
if (disabled) return <li className='print-center-item'>{item.title} </li>;
return (
<li className='print-center-item'>
{item.title}
<PrinterOutlined onClick={renderToNewWindow} />
<MailOutlined
onClick={() => {
setEmailOptions({
messageOptions: {
Subject: "TODO FIX ME",
},
template: {
name: item.key,
variables: { id: id },
},
});
}}
/>
</li>
);
} }
export default connect( export default connect(
mapStateToProps, mapStateToProps,

View File

@@ -1,6 +1,5 @@
import { Collapse } from "antd"; import { Collapse } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
@@ -10,8 +9,8 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import JobsReports from "./print-center-jobs.list";
import PrintCenterItem from "../print-center-item/print-center-item.component"; import PrintCenterItem from "../print-center-item/print-center-item.component";
import JobsReports from "./print-center-jobs.list";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -23,20 +22,23 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("printCenter")), toggleModalVisible: () => dispatch(toggleModalVisible("printCenter")),
}); });
export function PrintCenterJobsComponent({ printCenterModal }) { export function PrintCenterJobsComponent({ printCenterModal }) {
const { t } = useTranslation(); const { id: jobId } = printCenterModal.context;
const { id: jobid } = printCenterModal.context;
const JobsReportsList = JobsReports(null); const JobsReportsList = JobsReports(null);
return ( return (
<div> <div>
Print Center Jobs COmponetn
<Collapse accordion> <Collapse accordion>
{JobsReportsList.map((section) => ( {JobsReportsList.map((section) => (
<Collapse.Panel key={section.key} header={section.title}> <Collapse.Panel key={section.key} header={section.title}>
<ul style={{ columns: "2 auto" }}> <ul style={{ columns: "2 auto" }}>
{section.items.map((item) => ( {section.items.map((item) => (
<PrintCenterItem key={item.key} item={item} /> <PrintCenterItem
key={item.key}
item={item}
id={jobId}
disabled={item.disabled}
/>
))} ))}
</ul> </ul>
</Collapse.Panel> </Collapse.Panel>

View File

@@ -3,46 +3,56 @@ import i18n from "i18next";
export default function JobsReports(job) { export default function JobsReports(job) {
return [ return [
{ {
title: i18n.t("printcenter.jobs.repairorder"), title: i18n.t("printcenter.labels.repairorder"),
key: "printcenter.jobs.repairorder", key: "printcenter.jobs.repairorder",
disabled: false, disabled: false,
items: [ items: [
{ {
key: "appointment_reminder", key: "appointment_reminder",
title: "Appointment Reminder", title: i18n.t("printcenter.jobs.appointment_reminder"),
disabled: false, disabled: false,
}, },
{ {
key: "appointment_reminder2", key: "estimate_detail",
title: "Appointment Reminder2", title: i18n.t("printcenter.jobs.estimate_detail"),
disabled: false, disabled: false,
}, },
{ {
key: "appointment_reminder3", key: "job_totals",
title: "Appointment Reminder3", title: i18n.t("printcenter.jobs.job_totals"),
disabled: false, disabled: false,
}, },
{ {
key: "appointment_reminder4", key: "work_authorization",
title: "Appointment Reminder4", title: i18n.t("printcenter.jobs.work_authorization"),
disabled: false, disabled: false,
}, },
{ {
key: "appointment_reminder5", key: "fippa_work_authorization",
title: "Appointment Reminder5", title: i18n.t("printcenter.jobs.fippa_work_authorization"),
disabled: false,
},
{
key: "casl_work_authorization",
title: i18n.t("printcenter.jobs.casl_work_authorization"),
disabled: false,
},
{
key: "coversheet",
title: i18n.t("printcenter.jobs.coversheet"),
disabled: false, disabled: false,
}, },
], ],
}, },
{ {
title: "Section 2", title: i18n.t("printcenter.labels.misc"),
key: "Section2", key: "printcenter.jobs.misc",
disabled: false, disabled: false,
items: [ items: [
{ {
key: "appointment_reminder12", key: "iou",
title: "Appointment Reminder12", title: i18n.t("printcenter.jobs.iou"),
disabled: false, disabled: true,
}, },
], ],
}, },

View File

@@ -11,6 +11,7 @@ import {
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import PrintCenterModalComponent from "./print-center-modal.component"; import PrintCenterModalComponent from "./print-center-modal.component";
import "./print-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,

View File

@@ -0,0 +1,3 @@
.print-center-item > * {
padding: 0em 8px;
}

View File

@@ -12,4 +12,4 @@ export const endLoading = (options) => ({
export const setBreadcrumbs = (breadcrumbs) => ({ export const setBreadcrumbs = (breadcrumbs) => ({
type: ApplicationActionTypes.SET_BREAD_CRUMBS, type: ApplicationActionTypes.SET_BREAD_CRUMBS,
payload: breadcrumbs, payload: breadcrumbs,
}); });

View File

@@ -1,7 +1,4 @@
import { all, call, put, takeLatest } from "redux-saga/effects"; import { all } from "redux-saga/effects";
// import { sendEmailFailure, sendEmailSuccess } from "./email.actions";
// import EmailActionTypes from "./email.types";
import axios from "axios";
// export function* onSendEmail() { // export function* onSendEmail() {
// yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail); // yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail);
@@ -31,12 +28,12 @@ import axios from "axios";
// } // }
// } // }
// export function* onSendEmailFailure() { // export function* onRenderTemplate() {
// yield takeLatest(EmailActionTypes.SEND_EMAIL_FAILURE, sendEmailFailureSaga); // yield takeLatest(ApplicationActionTypes.RENDER_TEMPLATE, renderTemplate);
// } // }
// export function* sendEmailFailureSaga(error) { // export function* renderTemplate({ payload: template }) {
// try { // try {
// yield console.log(error); // yield console.log("Render Template Saga => template", template);
// } catch (error) { // } catch (error) {
// console.log("Error in sendEmailFailure saga.", error.message); // console.log("Error in sendEmailFailure saga.", error.message);
// } // }
@@ -47,5 +44,6 @@ export function* applicationSagas() {
// call(onSendEmail), // call(onSendEmail),
// call(onSendEmailFailure), // call(onSendEmailFailure),
// call(onSendEmailSuccess) // call(onSendEmailSuccess)
//call(onRenderTemplate),
]); ]);
} }

View File

@@ -1,51 +1,66 @@
import { all, call, put, takeLatest } from "redux-saga/effects"; import { all } from "redux-saga/effects";
import { sendEmailFailure, sendEmailSuccess } from "./email.actions"; // import { sendEmailFailure, sendEmailSuccess } from "./email.actions";
import EmailActionTypes from "./email.types"; // import { renderTemplate } from "../application/application.actions";
import axios from "axios"; // import EmailActionTypes from "./email.types";
// import axios from "axios";
export function* onSendEmail() { // export function* onSendEmail() {
yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail); // yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail);
} // }
export function* sendEmail(payload) { // export function* sendEmail(payload) {
try { // try {
console.log("Sending thta email", payload); // console.log("Sending thta email", payload);
axios.post("/sendemail", payload).then(response => { // axios.post("/sendemail", payload).then(response => {
console.log(JSON.stringify(response)); // console.log(JSON.stringify(response));
put(sendEmailSuccess()); // put(sendEmailSuccess());
}); // });
} catch (error) { // } catch (error) {
console.log("Error in sendEmail saga."); // console.log("Error in sendEmail saga.");
yield put(sendEmailFailure(error.message)); // yield put(sendEmailFailure(error.message));
} // }
} // }
export function* onSendEmailSuccess() { // export function* onSendEmailSuccess() {
yield takeLatest(EmailActionTypes.SEND_EMAIL_SUCCESS, sendEmailSuccessSaga); // yield takeLatest(EmailActionTypes.SEND_EMAIL_SUCCESS, sendEmailSuccessSaga);
} // }
export function* sendEmailSuccessSaga() { // export function* sendEmailSuccessSaga() {
try { // try {
console.log("Send email success."); // console.log("Send email success.");
} catch (error) { // } catch (error) {
console.log("Error in sendEmailSuccess saga."); // console.log("Error in sendEmailSuccess saga.");
yield put(sendEmailFailure(error.message)); // yield put(sendEmailFailure(error.message));
} // }
} // }
export function* onSendEmailFailure() { // export function* onSendEmailFailure() {
yield takeLatest(EmailActionTypes.SEND_EMAIL_FAILURE, sendEmailFailureSaga); // yield takeLatest(EmailActionTypes.SEND_EMAIL_FAILURE, sendEmailFailureSaga);
} // }
export function* sendEmailFailureSaga(error) { // export function* sendEmailFailureSaga(error) {
try { // try {
yield console.log(error); // yield console.log(error);
} catch (error) { // } catch (error) {
console.log("Error in sendEmailFailure saga.", error.message); // console.log("Error in sendEmailFailure saga.", error.message);
} // }
} //}
// export function* onSetEmailOptions() {
// yield takeLatest(EmailActionTypes.SET_EMAIL_OPTIONS, setEmailOptions);
// }
// export function* setEmailOptions({ payload: { template } }) {
// console.log("function*setEmailOptions -> template", template);
// try {
// yield put(renderTemplate(template));
// } catch (error) {
// console.log("Error in setEmailOptions saga.", error.message);
// }
// }
export function* emailSagas() { export function* emailSagas() {
yield all([ yield all([
call(onSendEmail), // call(onSendEmail),
call(onSendEmailFailure), // call(onSendEmailFailure),
call(onSendEmailSuccess) // call(onSendEmailSuccess)
//call(onSetEmailOptions),
]); ]);
} }

View File

@@ -737,9 +737,17 @@
"nocontexttype": "No context type set." "nocontexttype": "No context type set."
}, },
"jobs": { "jobs": {
"repairorder": "Repair Order Related" "appointment_reminder": "Appointment Reminder",
"casl_work_authorization": "CASL Work Authorization",
"coversheet": "Coversheet",
"estimate_detail": "Estimate Details",
"fippa_work_authorization": "FIPPA Work Authorization",
"job_totals": "Job Totals Only",
"work_authorization": "Work Authorization"
}, },
"labels": { "labels": {
"misc": "Miscellaneous Documents",
"repairorder": "Repair Order Related",
"title": "Print Center" "title": "Print Center"
} }
}, },

View File

@@ -737,9 +737,17 @@
"nocontexttype": "" "nocontexttype": ""
}, },
"jobs": { "jobs": {
"repairorder": "" "appointment_reminder": "",
"casl_work_authorization": "",
"coversheet": "",
"estimate_detail": "",
"fippa_work_authorization": "",
"job_totals": "",
"work_authorization": ""
}, },
"labels": { "labels": {
"misc": "",
"repairorder": "",
"title": "" "title": ""
} }
}, },

View File

@@ -737,9 +737,17 @@
"nocontexttype": "" "nocontexttype": ""
}, },
"jobs": { "jobs": {
"repairorder": "" "appointment_reminder": "",
"casl_work_authorization": "",
"coversheet": "",
"estimate_detail": "",
"fippa_work_authorization": "",
"job_totals": "",
"work_authorization": ""
}, },
"labels": { "labels": {
"misc": "",
"repairorder": "",
"title": "" "title": ""
} }
}, },

View File

@@ -0,0 +1,37 @@
import { gql } from "apollo-boost";
import { QUERY_TEMPLATES_BY_NAME } from "../graphql/templates.queries";
import axios from "axios";
export default async function RenderTemplate(templateObject, client, bodyshop) {
const { data: templateRecords } = await client.query({
query: QUERY_TEMPLATES_BY_NAME,
variables: { name: templateObject.name },
fetchPolicy: "network-only",
});
let templateToUse;
if (templateRecords.templates.length === 1) {
templateToUse = templateRecords.templates[0];
} else if (templateRecords.templates.length === 2) {
templateToUse = templateRecords.templates.filter((t) => !!t.bodyshopid);
} else {
//No template found.Uh oh.
alert("Template key does not exist.");
throw new Error("Template key does not exist.");
}
const { data: contextData } = await client.query({
query: gql(templateToUse.query),
variables: { ...templateObject.variables },
fetchPolicy: "network-only",
});
const { data } = await axios.post("/render", {
view: templateToUse.html,
context: { ...contextData, bodyshop: bodyshop },
});
return new Promise((resolve, reject) => {
resolve(data);
});
}