Email sending framework in place with nodemailer. Some rendering is happening dynamically, but i todesn't bring in most styles.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Input, Table, Button } from "antd";
|
||||
import { Button, Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
@@ -123,8 +123,9 @@ export default function JobLinesComponent({
|
||||
{record.allocations && record.allocations.length > 0
|
||||
? record.allocations.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
|
||||
key={
|
||||
item.id
|
||||
}>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
|
||||
))
|
||||
: null}
|
||||
<AllocationsAssignmentContainer
|
||||
@@ -184,8 +185,7 @@ export default function JobLinesComponent({
|
||||
/>
|
||||
<Button
|
||||
disabled={selectedLines.length > 0 ? false : true}
|
||||
onClick={() => setPartsModalVisible(true)}
|
||||
>
|
||||
onClick={() => setPartsModalVisible(true)}>
|
||||
{t("parts.actions.order")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -193,7 +193,7 @@ export default function JobLinesComponent({
|
||||
}}
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
size="small"
|
||||
size='small'
|
||||
pagination={{ position: "bottom", defaultPageSize: 50 }}
|
||||
rowSelection={{
|
||||
// selectedRowKeys: selectedLines,
|
||||
@@ -201,7 +201,7 @@ export default function JobLinesComponent({
|
||||
setSelectedLines(selectedRows)
|
||||
}}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey="id"
|
||||
rowKey='id'
|
||||
dataSource={jobLines}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
|
||||
@@ -56,6 +56,10 @@ export default connect(
|
||||
data: linesToOrder.reduce((acc, value) => {
|
||||
acc.push({
|
||||
line_desc: value.line_desc,
|
||||
oem_partno: value.oem_partno,
|
||||
db_price: value.db_price,
|
||||
act_price: value.act_price,
|
||||
line_remarks: "Alalala",
|
||||
joblineid: value.joblineid,
|
||||
status:
|
||||
bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||
|
||||
@@ -9,3 +9,47 @@ export const INSERT_NEW_PARTS_ORDERS = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REPORT_QUERY_PARTS_ORDER_BY_PK = gql`
|
||||
query REPORT_QUERY_PARTS_ORDER_BY_PK($id: uuid!) {
|
||||
parts_orders_by_pk(id: $id) {
|
||||
job {
|
||||
id
|
||||
vehicle {
|
||||
id
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
}
|
||||
ro_number
|
||||
est_number
|
||||
}
|
||||
id
|
||||
deliver_by
|
||||
parts_order_lines {
|
||||
id
|
||||
db_price
|
||||
act_price
|
||||
line_desc
|
||||
line_remarks
|
||||
oem_partno
|
||||
status
|
||||
}
|
||||
status
|
||||
user_email
|
||||
}
|
||||
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||
id
|
||||
address1
|
||||
address2
|
||||
city
|
||||
email
|
||||
federal_tax_id
|
||||
state
|
||||
shopname
|
||||
zip_post
|
||||
logo_img_path
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,42 @@
|
||||
import React from 'react'
|
||||
import { Button } from "antd";
|
||||
import axios from "axios";
|
||||
import React from "react";
|
||||
import { useApolloClient } from "react-apollo";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../graphql/parts-orders.queries";
|
||||
import ReportPartsOrder from "../../reports/pages/parts-order/parts-order.component";
|
||||
|
||||
export default function ManageRootPageComponent() {
|
||||
return (
|
||||
<div>
|
||||
Temporary Home Page.
|
||||
</div>
|
||||
)
|
||||
const client = useApolloClient();
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
client
|
||||
.query({
|
||||
query: REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||
variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" },
|
||||
fetchPolicy: "network-only"
|
||||
})
|
||||
.then(response => {
|
||||
axios.post("/sendemail", {
|
||||
from: { name: "Kavia Autobody" },
|
||||
to: "snaptsoft@gmail.com",
|
||||
replyTo: "patrickwf@gmail.com",
|
||||
subject: "Sending Email using Node.js",
|
||||
text: "Plaintext version of the message",
|
||||
html: ReactDOMServer.renderToStaticMarkup(
|
||||
<ReportPartsOrder
|
||||
order={response.data.parts_orders_by_pk}
|
||||
bodyshop={response.data.bodyshops[0]}
|
||||
/>
|
||||
)
|
||||
});
|
||||
});
|
||||
}}>
|
||||
Test Email
|
||||
</Button>
|
||||
<div>Testing Section-Report</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import { ApolloProvider } from "react-apollo";
|
||||
|
||||
export default function ReportEmailWrapper({ client, children }) {
|
||||
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { Row, Col, Typography } from "antd";
|
||||
export default function RcShopHeaderComponent({ bodyshop }) {
|
||||
if (!bodyshop) return null;
|
||||
return (
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
{bodyshop.logo_img_path ? (
|
||||
<img
|
||||
alt='Shop Logo'
|
||||
src={bodyshop.logo_img_path}
|
||||
style={{ height: "80px" }}
|
||||
/>
|
||||
) : null}
|
||||
</Col>
|
||||
<Col span={16}>
|
||||
<Row>
|
||||
<Typography.Title>{`${bodyshop.shopname}`}</Typography.Title>
|
||||
</Row>
|
||||
<Row>{`${bodyshop.address1 || ""} ${bodyshop.address2 ||
|
||||
""}, ${bodyshop.city || ""}, ${bodyshop.state ||
|
||||
""} ${bodyshop.zip_post || ""}`}</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
import { Row, Col, Table } from "antd";
|
||||
import CurrencyFormatter from "../../../utils/CurrencyFormatter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ReportPartsOrderComponent({ order, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc"
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.oem_partno"),
|
||||
dataIndex: "oem_partno",
|
||||
key: "oem_partno",
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
{record.oem_partno ? record.oem_partno : record.op_code_desc}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.line_remarks"),
|
||||
dataIndex: "line_remarks",
|
||||
key: "line_remarks"
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.db_price"),
|
||||
dataIndex: "db_price",
|
||||
key: "db_price",
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
key: "act_price",
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Col span={4}>
|
||||
<div>{`PO Number: ${order.po_number || ""}`}</div>
|
||||
<div>{`Delivery By: ${order.deliver_by || ""}`}</div>
|
||||
<div>{`Ordered By: ${order.user_email || ""}`}</div>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<div>{`Vehicle Description: ${order.job.vehicle.v_model_yr ||
|
||||
""} ${order.job.vehicle.v_make_desc || ""} ${order.job.vehicle
|
||||
.v_model_desc || ""}`}</div>
|
||||
<div>{`VIN: ${order.job.vehicle.v_vin || ""}`}</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Table
|
||||
size='small'
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
pagination={false}
|
||||
rowKey='id'
|
||||
dataSource={order.parts_order_lines ? order.parts_order_lines : null}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "react-apollo";
|
||||
import AlertComponent from "../../../components/alert/alert.component";
|
||||
import LoadingSpinner from "../../../components/loading-spinner/loading-spinner.component";
|
||||
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../../graphql/parts-orders.queries";
|
||||
import RcShopHeaderComponent from "../../components/shop-header/shop-header.component";
|
||||
import ReportPartsOrderComponent from "./parts-order.component";
|
||||
|
||||
export default function ReportPartsOrderContainer({ orderId }) {
|
||||
const { loading, error, data } = useQuery(REPORT_QUERY_PARTS_ORDER_BY_PK, {
|
||||
variables: { id: orderId },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
if (loading) return <LoadingSpinner />;
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
return (
|
||||
<div>
|
||||
<RcShopHeaderComponent bodyshop={data ? data.bodyshops[0] : null} />
|
||||
<ReportPartsOrderComponent
|
||||
order={data ? data.parts_orders_by_pk : null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -11196,8 +11196,6 @@ rxjs@^6.5.3:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
||||
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"dotenv": "8.2.0",
|
||||
"express": "^4.16.4",
|
||||
"express-sslify": "^1.2.0",
|
||||
"firebase-tools": "^7.12.1"
|
||||
"firebase-tools": "^7.12.1",
|
||||
"nodemailer": "^6.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^4.0.1",
|
||||
|
||||
45
sendemail.js
Normal file
45
sendemail.js
Normal file
@@ -0,0 +1,45 @@
|
||||
var nodemailer = require("nodemailer");
|
||||
require("dotenv").config();
|
||||
|
||||
var transporter = nodemailer.createTransport({
|
||||
host: process.env.email_server,
|
||||
port: 465,
|
||||
secure: true, // upgrade later with STARTTLS
|
||||
auth: {
|
||||
user: process.env.email_api,
|
||||
pass: process.env.email_secret
|
||||
}
|
||||
});
|
||||
|
||||
// verify connection configuration
|
||||
transporter.verify(function(error, success) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
} else {
|
||||
console.log("[EMAIL] Succesfully connected to SMTP server.");
|
||||
}
|
||||
});
|
||||
|
||||
exports.sendEmail = (req, res) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.log("[EMAIL] Incoming Message Body", req.body);
|
||||
}
|
||||
transporter.sendMail(
|
||||
{
|
||||
...req.body,
|
||||
from: {
|
||||
name: req.body.from.name || "No Reply @ Bodyshop.app",
|
||||
address: "noreply@bodyshop.app"
|
||||
}
|
||||
},
|
||||
function(error, info) {
|
||||
if (error) {
|
||||
console.log("[EMAIL] Email send failed. ", error);
|
||||
res.json({ success: false, error: error });
|
||||
} else {
|
||||
console.log("[EMAIL] Email sent: " + info.response);
|
||||
res.json({ success: true, response: info.response });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -23,6 +23,8 @@ app.post("/sign_s3", s3upload.sign_s3);
|
||||
app.get("/sign_s3", s3upload.get_s3);
|
||||
app.post("/delete_s3", s3upload.delete_s3);
|
||||
|
||||
var sendEmail = require("./sendemail.js");
|
||||
app.post("/sendemail", sendEmail.sendEmail);
|
||||
// app.get("/test", function(req, res) {
|
||||
// res.json({ success: true });
|
||||
// });
|
||||
|
||||
@@ -2872,6 +2872,11 @@ node-forge@^0.9.0:
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
|
||||
integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
|
||||
|
||||
nodemailer@^6.4.2:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.2.tgz#7147550e32cdc37453380ab78d2074533966090a"
|
||||
integrity sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ==
|
||||
|
||||
normalize-package-data@^2.3.2:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||
|
||||
Reference in New Issue
Block a user