Added image upload to email and multiple recipients BOD-103

This commit is contained in:
Patrick Fic
2020-07-23 11:27:09 -07:00
parent 3eb2c0d787
commit 8a4f0dda44
12 changed files with 556 additions and 76 deletions

View File

@@ -4,17 +4,21 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { setEmailOptions } from "../../redux/email/email.actions";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function Test({ bodyshop }) {
)(function Test({ bodyshop, setEmailOptions }) {
const handle = async () => {
const response = await axios.post(
"/accounting/iif/receivables",
@@ -94,6 +98,24 @@ export default connect(
return (
<div>
<button
onClick={() => {
setEmailOptions({
messageOptions: {
to: ["patrickwf@gmail.com"],
replyTo: bodyshop.email,
},
template: {
name: TemplateList.parts_order_confirmation.key,
variables: {
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
},
},
});
}}
>
send email
</button>
<button onClick={handle}>Hit with Header.</button>
<button onClick={handleLocal}>Hit Localhost with Header.</button>
<button onClick={handleQbxml}>Qbxml</button>

View File

@@ -1,18 +1,26 @@
import { UploadOutlined } from "@ant-design/icons";
import { Editor } from "@tinymce/tinymce-react";
import { Input } from "antd";
import { Button, Input, Upload, Select } from "antd";
import React from "react";
export default function EmailOverlayComponent({
messageOptions,
handleConfigChange,
handleToChange,
handleHtmlChange,
handleUpload,
handleFileRemove,
}) {
return (
<div>
To:
<Input
value={messageOptions.to}
onChange={handleConfigChange}
<Select
name="to"
mode="tags"
value={messageOptions.to}
style={{ width: "100%" }}
onChange={handleToChange}
tokenSeparators={[",", ";"]}
/>
CC:
<Input
@@ -45,6 +53,17 @@ export default function EmailOverlayComponent({
}}
onEditorChange={handleHtmlChange}
/>
<Upload
fileList={messageOptions.fileList}
beforeUpload={handleUpload}
onRemove={handleFileRemove}
multiple
listType="picture-card"
>
<Button>
<UploadOutlined /> Upload
</Button>
</Upload>
</div>
);
}

View File

@@ -4,17 +4,17 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { toggleEmailOverlayVisible } from "../../redux/email/email.actions";
import {
selectEmailConfig,
selectEmailVisible,
} from "../../redux/email/email.selectors.js";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { EmailSettings } from "../../utils/TemplateConstants";
import RenderTemplate from "../../utils/RenderTemplate";
import { EmailSettings } from "../../utils/TemplateConstants";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import EmailOverlayComponent from "./email-overlay.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
modalVisible: selectEmailVisible,
@@ -33,6 +33,7 @@ export function EmailOverlayContainer({
bodyshop,
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [sending, setSending] = useState(false);
const defaultEmailFrom = {
@@ -45,14 +46,27 @@ export function EmailOverlayContainer({
const [messageOptions, setMessageOptions] = useState({
...defaultEmailFrom,
html: "",
fileList: [],
});
const handleOk = async () => {
logImEXEvent("email_send_from_modal");
const attachments = [];
await asyncForEach(messageOptions.fileList, async (f) => {
const t = {
ContentType: f.type,
Filename: f.name,
Base64Content: (await toBase64(f)).split(",")[1],
};
attachments.push(t);
});
console.log("messageOptions", messageOptions);
setSending(true);
try {
await axios.post("/sendemail", messageOptions);
await axios.post("/sendemail", { ...messageOptions, attachments });
notification["success"]({ message: t("emails.successes.sent") });
toggleEmailOverlayVisible();
} catch (error) {
@@ -71,17 +85,37 @@ export function EmailOverlayContainer({
const handleHtmlChange = (text) => {
setMessageOptions({ ...messageOptions, html: text });
};
const handleToChange = (recipients) => {
setMessageOptions({ ...messageOptions, to: recipients });
};
const handleUpload = (file) => {
setMessageOptions({
...messageOptions,
fileList: [...messageOptions.fileList, file],
});
return false;
};
const handleFileRemove = (file) => {
setMessageOptions((state) => {
const index = state.fileList.indexOf(file);
const newfileList = state.fileList.slice();
newfileList.splice(index, 1);
return {
fileList: newfileList,
};
});
};
const render = async () => {
logImEXEvent("email_render_template", { template: emailConfig.template });
setLoading(true);
console.log("emailConfig", emailConfig);
let html = await RenderTemplate(emailConfig.template, bodyshop);
setMessageOptions({
...emailConfig.messageOptions,
...defaultEmailFrom,
html: html,
fileList: [],
});
setLoading(false);
};
@@ -99,17 +133,22 @@ export function EmailOverlayContainer({
onCancel={() => {
toggleEmailOverlayVisible();
}}
okButtonProps={{ loading: sending }}>
okButtonProps={{ loading: sending }}
>
<LoadingSpinner loading={loading}>
<EmailOverlayComponent
handleConfigChange={handleConfigChange}
messageOptions={messageOptions}
handleHtmlChange={handleHtmlChange}
handleUpload={handleUpload}
handleFileRemove={handleFileRemove}
handleToChange={handleToChange}
/>
<button
onClick={() => {
navigator.clipboard.writeText(messageOptions.html);
}}>
}}
>
Copy HTML
</button>
</LoadingSpinner>
@@ -120,3 +159,17 @@ export default connect(
mapStateToProps,
mapDispatchToProps
)(EmailOverlayContainer);
const toBase64 = (file) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};

View File

@@ -83,7 +83,7 @@ export function JobsDetailHeaderCsi({
if (e.key === "email")
setEmailOptions({
messageOptions: {
to: job.ownr_ea,
to: [job.ownr_ea],
replyTo: bodyshop.email,
},
template: {

View File

@@ -148,7 +148,7 @@ export function PartsOrderModalContainer({
if (sendType === "e") {
setEmailOptions({
messageOptions: {
to: data.vendors.filter((item) => item.id === values.id)[0] || null,
to: [data.vendors.filter((item) => item.id === values.id)[0]] || null,
replyTo: bodyshop.email,
},
template: {
@@ -216,17 +216,19 @@ export function PartsOrderModalContainer({
: t("parts_orders.labels.newpartsorder")
}
onCancel={() => toggleModalVisible()}
width='90%'
width="90%"
onOk={() => form.submit()}
destroyOnClose
forceRender>
{error ? <AlertComponent message={error.message} type='error' /> : null}
forceRender
>
{error ? <AlertComponent message={error.message} type="error" /> : null}
<LoadingSpinner loading={loading}>
<Form
form={form}
autoComplete='no'
autoComplete="no"
onFinish={handleFinish}
initialValues={initialValues}>
initialValues={initialValues}
>
<PartsOrderModalComponent
vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState}

View File

@@ -120,7 +120,7 @@ function InvoiceEnterModalContainer({
if (sendby === "email") {
setEmailOptions({
messageOptions: {
// to: appData.email,
// to: [appData.email],
replyTo: bodyshop.email,
},
template: {

View File

@@ -135,7 +135,7 @@ export function ScheduleJobModalContainer({
if (appData.notifyCustomer) {
setEmailOptions({
messageOptions: {
to: appData.email,
to: [appData.email],
replyTo: bodyshop.email,
},
template: {

View File

@@ -175,7 +175,7 @@ export function Manage({ match, conflict }) {
<Route exact path={`${match.path}`} component={ManageRootPage} />
<Route
exact
path={`${match.path}/ttt`}
path={`${match.path}/_test`}
component={TestComponent}
/>
<Route exact path={`${match.path}/jobs`} component={JobsPage} />