From 81e3ea622f248fb7316f02b8ef53ffb979298792 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 18 Feb 2020 17:32:43 -0800 Subject: [PATCH] Email sending framework in place with nodemailer. Some rendering is happening dynamically, but i todesn't bring in most styles. --- .../job-detail-lines/job-lines.component.jsx | 14 ++-- .../parts-order-modal.container.jsx | 4 + client/src/graphql/parts-orders.queries.js | 44 +++++++++++ .../manage-root.page.component.jsx | 45 ++++++++++-- .../email-wrapper/email-wrapper.component.jsx | 6 ++ .../shop-header/shop-header.component.jsx | 26 +++++++ .../parts-order/parts-order.component.jsx | 73 +++++++++++++++++++ .../parts-order/parts-order.container.jsx | 24 ++++++ client/yarn.lock | 2 - package.json | 3 +- sendemail.js | 45 ++++++++++++ server.js | 2 + yarn.lock | 5 ++ 13 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 client/src/reports/components/email-wrapper/email-wrapper.component.jsx create mode 100644 client/src/reports/components/shop-header/shop-header.component.jsx create mode 100644 client/src/reports/pages/parts-order/parts-order.component.jsx create mode 100644 client/src/reports/pages/parts-order/parts-order.container.jsx create mode 100644 sendemail.js diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 3f61cfdd7..0b11301b3 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -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 => (
{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}
+ key={ + item.id + }>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`} )) : null} @@ -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} /> diff --git a/client/src/components/parts-order-modal/parts-order-modal.container.jsx b/client/src/components/parts-order-modal/parts-order-modal.container.jsx index 902e8e25b..06cccf380 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.container.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.container.jsx @@ -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*" diff --git a/client/src/graphql/parts-orders.queries.js b/client/src/graphql/parts-orders.queries.js index b2124383c..34eb515e1 100644 --- a/client/src/graphql/parts-orders.queries.js +++ b/client/src/graphql/parts-orders.queries.js @@ -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 + } + } +`; diff --git a/client/src/pages/manage-root/manage-root.page.component.jsx b/client/src/pages/manage-root/manage-root.page.component.jsx index a7ff94d11..4d2ba2c2c 100644 --- a/client/src/pages/manage-root/manage-root.page.component.jsx +++ b/client/src/pages/manage-root/manage-root.page.component.jsx @@ -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 ( -
- Temporary Home Page. -
- ) + const client = useApolloClient(); + return ( +
+ +
Testing Section-Report
+
+ ); } diff --git a/client/src/reports/components/email-wrapper/email-wrapper.component.jsx b/client/src/reports/components/email-wrapper/email-wrapper.component.jsx new file mode 100644 index 000000000..3e84c11bf --- /dev/null +++ b/client/src/reports/components/email-wrapper/email-wrapper.component.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import { ApolloProvider } from "react-apollo"; + +export default function ReportEmailWrapper({ client, children }) { + return {children}; +} diff --git a/client/src/reports/components/shop-header/shop-header.component.jsx b/client/src/reports/components/shop-header/shop-header.component.jsx new file mode 100644 index 000000000..9561f1853 --- /dev/null +++ b/client/src/reports/components/shop-header/shop-header.component.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Row, Col, Typography } from "antd"; +export default function RcShopHeaderComponent({ bodyshop }) { + if (!bodyshop) return null; + return ( + + + {bodyshop.logo_img_path ? ( + Shop Logo + ) : null} + + + + {`${bodyshop.shopname}`} + + {`${bodyshop.address1 || ""} ${bodyshop.address2 || + ""}, ${bodyshop.city || ""}, ${bodyshop.state || + ""} ${bodyshop.zip_post || ""}`} + + + ); +} diff --git a/client/src/reports/pages/parts-order/parts-order.component.jsx b/client/src/reports/pages/parts-order/parts-order.component.jsx new file mode 100644 index 000000000..94864df10 --- /dev/null +++ b/client/src/reports/pages/parts-order/parts-order.component.jsx @@ -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) => ( + + {record.oem_partno ? record.oem_partno : record.op_code_desc} + + ) + }, + { + 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) => ( + {record.db_price} + ) + }, + { + title: t("joblines.fields.act_price"), + dataIndex: "act_price", + key: "act_price", + render: (text, record) => ( + {record.act_price} + ) + } + ]; + + return ( +
+ + +
{`PO Number: ${order.po_number || ""}`}
+
{`Delivery By: ${order.deliver_by || ""}`}
+
{`Ordered By: ${order.user_email || ""}`}
+ + +
{`Vehicle Description: ${order.job.vehicle.v_model_yr || + ""} ${order.job.vehicle.v_make_desc || ""} ${order.job.vehicle + .v_model_desc || ""}`}
+
{`VIN: ${order.job.vehicle.v_vin || ""}`}
+ +
+ + ({ ...item }))} + pagination={false} + rowKey='id' + dataSource={order.parts_order_lines ? order.parts_order_lines : null} + /> + + + ); +} diff --git a/client/src/reports/pages/parts-order/parts-order.container.jsx b/client/src/reports/pages/parts-order/parts-order.container.jsx new file mode 100644 index 000000000..5ccfc8893 --- /dev/null +++ b/client/src/reports/pages/parts-order/parts-order.container.jsx @@ -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 ; + if (error) return ; + return ( +
+ + +
+ ); +} diff --git a/client/yarn.lock b/client/yarn.lock index ed969110b..d0b1a823a 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -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" diff --git a/package.json b/package.json index f2140eed1..ebb9e31f7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/sendemail.js b/sendemail.js new file mode 100644 index 000000000..868c6b65b --- /dev/null +++ b/sendemail.js @@ -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 }); + } + } + ); +}; diff --git a/server.js b/server.js index 7acc0a49d..258d70662 100644 --- a/server.js +++ b/server.js @@ -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 }); // }); diff --git a/yarn.lock b/yarn.lock index 48163623a..c72d7c808 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"