diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 0a097dcfb..9831a14ef 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -47,6 +47,74 @@ + + errors + + + deleting + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + saving + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + validation + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + fields @@ -73,6 +141,53 @@ + + successes + + + deleted + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + save + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + @@ -722,6 +837,63 @@ + + emails + + + errors + + + notsent + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + successes + + + sent + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + employees @@ -1578,6 +1750,79 @@ joblines + + actions + + + new + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + errors + + + creating + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + updating + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + fields @@ -1644,6 +1889,27 @@ + + line_ind + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + mod_lb_hrs false @@ -1665,6 +1931,27 @@ + + mod_lbr_ty + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + oem_partno false @@ -1686,6 +1973,27 @@ + + op_code_desc + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + part_type false @@ -1707,6 +2015,27 @@ + + status + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + unq_seq false @@ -1730,6 +2059,100 @@ + + labels + + + edit + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + new + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + successes + + + created + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + updated + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + @@ -5173,6 +5596,58 @@ + + messaging + + + labels + + + messaging + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + typeamessage + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + notes @@ -5979,6 +6454,27 @@ + + lineremarks + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -6005,6 +6501,48 @@ + + inthisorder + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + orderhistory + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + print false diff --git a/client/src/components/_test/test.component.jsx b/client/src/components/_test/test.component.jsx new file mode 100644 index 000000000..033f0ed3f --- /dev/null +++ b/client/src/components/_test/test.component.jsx @@ -0,0 +1,37 @@ +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { setEmailOptions } from "../../redux/email/email.actions"; +import T from "../../emails/parts-order/parts-order.email"; +import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query"; + +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser +}); +const mapDispatchToProps = dispatch => ({ + setEmailOptions: e => dispatch(setEmailOptions(e)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(function Test({ setEmailOptions }) { + return ( + + ); +}); diff --git a/client/src/components/allocations-assignment/allocations-assignment.container.jsx b/client/src/components/allocations-assignment/allocations-assignment.container.jsx index 434f71ecb..461b06627 100644 --- a/client/src/components/allocations-assignment/allocations-assignment.container.jsx +++ b/client/src/components/allocations-assignment/allocations-assignment.container.jsx @@ -20,14 +20,19 @@ export default function AllocationsAssignmentContainer({ const [insertAllocation] = useMutation(INSERT_ALLOCATION); const handleAssignment = () => { - insertAllocation({ variables: { alloc: { ...assignment } } }).then(r => { - notification["success"]({ - message: t("employees.successes.save") + insertAllocation({ variables: { alloc: { ...assignment } } }) + .then(r => { + notification["success"]({ + message: t("allocations.successes.save") + }); + visibilityState[1](false); + if (refetch) refetch(); + }) + .catch(error => { + notification["error"]({ + message: t("employees.errors.saving", { message: error.message }) + }); }); - //TODO Better way to reset the field decorators? - visibilityState[1](false); - if (refetch) refetch(); - }); }; return ( diff --git a/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx new file mode 100644 index 000000000..4682320d2 --- /dev/null +++ b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.component.jsx @@ -0,0 +1,67 @@ +import { Button, Popover, Select } from "antd"; +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"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +export default connect( + mapStateToProps, + null +)(function AllocationsBulkAssignmentComponent({ + disabled, + bodyshop, + handleAssignment, + assignment, + setAssignment, + visibilityState +}) { + const { t } = useTranslation(); + + const onChange = e => { + console.log("e", e); + setAssignment({ ...assignment, employeeid: e }); + }; + + const [visibility, setVisibility] = visibilityState; + + const popContent = ( +
+ + + + +
+ ); + + return ( + + + + ); +}); diff --git a/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx new file mode 100644 index 000000000..79d4540ea --- /dev/null +++ b/client/src/components/allocations-bulk-assignment/allocations-bulk-assignment.container.jsx @@ -0,0 +1,47 @@ +import React, { useState } from "react"; +import AllocationsBulkAssignment from "./allocations-bulk-assignment.component"; +import { useMutation } from "react-apollo"; +import { INSERT_ALLOCATION } from "../../graphql/allocations.queries"; +import { useTranslation } from "react-i18next"; +import { notification } from "antd"; + +export default function AllocationsBulkAssignmentContainer({ + jobLines, + refetch +}) { + const visibilityState = useState(false); + const { t } = useTranslation(); + const [assignment, setAssignment] = useState({ + employeeid: null + }); + const [insertAllocation] = useMutation(INSERT_ALLOCATION); + + const handleAssignment = () => { + const allocs = jobLines.reduce((acc, value) => { + acc.push({ + joblineid: value.id, + hours: parseFloat(value.mod_lb_hrs) || 0, + employeeid: assignment.employeeid + }); + return acc; + }, []); + + insertAllocation({ variables: { alloc: allocs } }).then(r => { + notification["success"]({ + message: t("employees.successes.save") + }); + visibilityState[1](false); + if (refetch) refetch(); + }); + }; + + return ( + 0 ? false : true} + handleAssignment={handleAssignment} + assignment={assignment} + setAssignment={setAssignment} + visibilityState={visibilityState} + /> + ); +} diff --git a/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx b/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx new file mode 100644 index 000000000..e67be1dcc --- /dev/null +++ b/client/src/components/allocations-employee-label/allocations-employee-label.component.jsx @@ -0,0 +1,19 @@ +import { Icon } from "antd"; +import React from "react"; +import { MdRemoveCircleOutline } from "react-icons/md"; + +export default function AllocationsLabelComponent({ allocation, handleClick }) { + return ( +
+ + {`${allocation.employee.first_name || ""} ${allocation.employee + .last_name || ""} (${allocation.hours || ""})`} + + +
+ ); +} diff --git a/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx b/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx new file mode 100644 index 000000000..bfd004a37 --- /dev/null +++ b/client/src/components/allocations-employee-label/allocations-employee-label.container.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useMutation } from "react-apollo"; +import { DELETE_ALLOCATION } from "../../graphql/allocations.queries"; +import AllocationsLabelComponent from "./allocations-employee-label.component"; +import { notification } from "antd"; +import { useTranslation } from "react-i18next"; + +export default function AllocationsLabelContainer({ allocation, refetch }) { + const [deleteAllocation] = useMutation(DELETE_ALLOCATION); + const { t } = useTranslation(); + const handleClick = e => { + e.preventDefault(); + deleteAllocation({ variables: { id: allocation.id } }) + .then(r => { + notification["success"]({ + message: t("allocations.successes.deleted") + }); + if (refetch) refetch(); + }) + .catch(error => { + notification["error"]({ message: t("allocations.errors.deleting") }); + }); + }; + return ( + + ); +} diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx new file mode 100644 index 000000000..5d7f095c6 --- /dev/null +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -0,0 +1,120 @@ +import { Button, Card, Input, Icon } from "antd"; +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import twilio from "twilio"; +import { + closeConversation, + toggleConversationVisible +} from "../../redux/messaging/messaging.actions"; +import PhoneFormatter from "../../utils/PhoneFormatter"; +import "./chat-conversation.styles.scss"; //https://bootsnipp.com/snippets/exR5v +import { MdSend } from "react-icons/md"; +import { useTranslation } from "react-i18next"; + +const client = twilio( + "ACf1b1aaf0e04740828b49b6e58467d787", + "0bea5e29a6d77593183ab1caa01d23de" +); + +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser +}); +const mapDispatchToProps = dispatch => ({ + toggleConversationVisible: conversationId => + dispatch(toggleConversationVisible(conversationId)), + closeConversation: phone => dispatch(closeConversation(phone)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(function ChatConversationComponent({ + conversation, + toggleConversationVisible, + closeConversation +}) { + const { t } = useTranslation(); + + const [messages, setMessages] = useState([]); + + useEffect(() => { + client.messages.list({ limit: 20 }, (error, items) => { + setMessages( + items.reduce((acc, value) => { + acc.push({ + sid: value.sid, + direction: value.direction, + body: value.body + }); + return acc; + }, []) + ); + }); + return () => {}; + }, [setMessages]); + return ( +
+ +
toggleConversationVisible(conversation.phone)} + > + {conversation.phone} +
+ +
+ ) : null + } + style={{ + width: conversation.open ? "400px" : "175px", + margin: "0px 10px" + }} + size="small" + > + {conversation.open ? ( +
+
+
    + {messages.map(item => ( +
  • +

    {item.body}

    +
  • + ))} +
+
+ } + /> +
+ ) : ( +
+
toggleConversationVisible(conversation.phone)}> + {conversation.phone} +
+ +
+ )} + + + ); +}); diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx new file mode 100644 index 000000000..bce58d405 --- /dev/null +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import ChatConversationComponent from "./chat-conversation.component"; + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser +}); +const mapDispatchToProps = dispatch => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(function ChatConversationContainer({ conversation }) { + return ; +}); diff --git a/client/src/components/chat-window/chat-window.styles.scss b/client/src/components/chat-conversation/chat-conversation.styles.scss similarity index 99% rename from client/src/components/chat-window/chat-window.styles.scss rename to client/src/components/chat-conversation/chat-conversation.styles.scss index 1b68074fb..a7e128649 100644 --- a/client/src/components/chat-window/chat-window.styles.scss +++ b/client/src/components/chat-conversation/chat-conversation.styles.scss @@ -20,7 +20,7 @@ .messages ul li { display: inline-block; clear: both; - float: left; + //float: left; margin: 5px 15px 5px 15px; width: calc(100% - 25px); font-size: 0.9em; diff --git a/client/src/components/chat-open-button/chat-open-button.component.jsx b/client/src/components/chat-open-button/chat-open-button.component.jsx new file mode 100644 index 000000000..bce620e7b --- /dev/null +++ b/client/src/components/chat-open-button/chat-open-button.component.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { openConversation } from "../../redux/messaging/messaging.actions"; +import { Icon } from "antd"; +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser +}); +const mapDispatchToProps = dispatch => ({ + openConversation: phone => dispatch(openConversation(phone)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(function ChatOpenButton({ openConversation, phone }) { + return ( + openConversation(phone)} + /> + ); +}); diff --git a/client/src/components/chat-overlay/chat-overlay.component.jsx b/client/src/components/chat-overlay/chat-overlay.component.jsx new file mode 100644 index 000000000..396a57df6 --- /dev/null +++ b/client/src/components/chat-overlay/chat-overlay.component.jsx @@ -0,0 +1,36 @@ +import { Badge, Card, Icon } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +export default function ChatWindowComponent({ + chatVisible, + toggleChatVisible +}) { + const { t } = useTranslation(); + return ( +
+ + toggleChatVisible()} + style={{ + width: chatVisible ? "300px" : "125px", + margin: "0px 10px" + }} + size="small" + > + {chatVisible ? ( +
+ List of chats here. +
+ ) : ( +
+ + + {t("messaging.labels.messaging")} + +
+ )} +
+
+
+ ); +} diff --git a/client/src/components/chat-overlay/chat-overlay.container.jsx b/client/src/components/chat-overlay/chat-overlay.container.jsx new file mode 100644 index 000000000..6302bad0d --- /dev/null +++ b/client/src/components/chat-overlay/chat-overlay.container.jsx @@ -0,0 +1,48 @@ +import { Affix, Badge } from "antd"; +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; +import { + selectChatVisible, + selectConversations +} from "../../redux/messaging/messaging.selectors"; +import ChatConversationContainer from "../chat-conversation/chat-conversation.container"; +import ChatOverlayComponent from "./chat-overlay.component"; + +const mapStateToProps = createStructuredSelector({ + chatVisible: selectChatVisible, + conversations: selectConversations +}); + +const mapDispatchToProps = dispatch => ({ + toggleChatVisible: () => dispatch(toggleChatVisible()) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(function ChatWindowContainer({ + chatVisible, + toggleChatVisible, + conversations +}) { + return ( + +
+ + + + {conversations + ? conversations.map((conversation, idx) => ( + + + + )) + : null} +
+
+ ); +}); diff --git a/client/src/components/chat-window/chat-window.component.jsx b/client/src/components/chat-window/chat-window.component.jsx deleted file mode 100644 index 53b5f790e..000000000 --- a/client/src/components/chat-window/chat-window.component.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Card } from "antd"; -import React, { useState, useEffect } from "react"; -import "./chat-window.styles.scss"; //https://bootsnipp.com/snippets/exR5v -import twilio from "twilio"; -// const client = require("twilio")( -// "ACf1b1aaf0e04740828b49b6e58467d787", -// "0bea5e29a6d77593183ab1caa01d23de" -// ); -const client = twilio( - "ACf1b1aaf0e04740828b49b6e58467d787", - "0bea5e29a6d77593183ab1caa01d23de" -); -export default function ChatWindowComponent({ toggleChatVisible }) { - const [conversations, setConversations] = useState([]); - - useEffect(() => { - client.messages.list({ limit: 20 }, (error, items) => { - setConversations( - items.reduce((acc, value) => { - acc.push({ - sid: value.sid, - direction: value.direction, - body: value.body - }); - return acc; - }, []) - ); - }); - return () => {}; - }, [setConversations]); - - console.log(conversations); - return ( - -
- -
-
    - {conversations.map(item => ( -
  • -

    {item.body}

    -
  • - ))} -
-
-
-
- ); -} diff --git a/client/src/components/chat-window/chat-window.container.jsx b/client/src/components/chat-window/chat-window.container.jsx deleted file mode 100644 index effb4c00b..000000000 --- a/client/src/components/chat-window/chat-window.container.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Affix, Button, Badge } from "antd"; -import React from "react"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; -import { selectChatVisible } from "../../redux/messaging/messaging.selectors"; -import ChatWindowComponent from "./chat-window.component"; - -const mapStateToProps = createStructuredSelector({ - chatVisible: selectChatVisible -}); - -const mapDispatchToProps = dispatch => ({ - toggleChatVisible: () => dispatch(toggleChatVisible()) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(function ChatWindowContainer({ chatVisible, toggleChatVisible }) { - return ( - - {chatVisible ? ( - - ) : ( - - + + ); +}); diff --git a/client/src/components/form-items-formatted/reset-form-item.component.jsx b/client/src/components/form-items-formatted/reset-form-item.component.jsx index b0ef84a84..54ef8e357 100644 --- a/client/src/components/form-items-formatted/reset-form-item.component.jsx +++ b/client/src/components/form-items-formatted/reset-form-item.component.jsx @@ -10,7 +10,7 @@ export default function ResetForm({ resetFields }) { message={
{t("general.messages.unsavedchanges")} -
diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 094b5f448..fe0a4997c 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -15,107 +15,168 @@ export default ({ }) => { const { t } = useTranslation(); //TODO Add + return ( - + {logo ? ( - - Shop Logo + + Shop Logo ) : null} - - - - - {t("menus.header.home")} - - - - - - - {t("menus.header.schedule")} - - - - {t("menus.header.activejobs")} - - - - {t("menus.header.availablejobs")} - - - + {landingHeader ? ( + + - - - - - {t("menus.header.owners")} - - - - - - {t("menus.header.vehicles")} - - - - - - {t("menus.header.shop_config")} - - - - {t("menus.header.shop_vendors")} - - - - - - - {currentUser.displayName || t("general.labels.unknown")} - - }> - - {t("user.actions.signout")} - - - {t("menus.currentuser.profile")} - - - {t("menus.currentuser.languageselector")} - - }> - - {t("general.languages.english")} +
+ + {currentUser.displayName || t("general.labels.unknown")} +
+ } + > + + {t("user.actions.signout")} - - {t("general.languages.french")} + + + {t("menus.currentuser.profile")} + - - {t("general.languages.spanish")} + + + {t("menus.currentuser.languageselector")} + + } + > + + {t("general.languages.english")} + + + {t("general.languages.french")} + + + {t("general.languages.spanish")} + + +
+
+ ) : ( + + + + + {t("menus.header.home")} + + + + + + + {t("menus.header.schedule")} + + + + {t("menus.header.activejobs")} + + + + {t("menus.header.availablejobs")} + - - + + + + + {t("menus.header.owners")} + + + + + + {t("menus.header.vehicles")} + + + + + + {t("menus.header.shop_config")} + + + + {t("menus.header.shop_vendors")} + + + + + + + {currentUser.displayName || t("general.labels.unknown")} + + } + > + + {t("user.actions.signout")} + + + + {t("menus.currentuser.profile")} + + + + + {t("menus.currentuser.languageselector")} + + } + > + + {t("general.languages.english")} + + + {t("general.languages.french")} + + + {t("general.languages.spanish")} + + + +
+ )} - {!landingHeader ? null : }
); }; diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.component.jsx b/client/src/components/invoice-enter-modal/invoice-enter-modal.component.jsx new file mode 100644 index 000000000..387290e7f --- /dev/null +++ b/client/src/components/invoice-enter-modal/invoice-enter-modal.component.jsx @@ -0,0 +1,71 @@ +import { Modal, Form, Input, InputNumber } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import ResetForm from "../form-items-formatted/reset-form-item.component"; + +export default function InvoiceEnterModalComponent({ + visible, + invoice, + handleCancel, + handleSubmit, + form +}) { + const { t } = useTranslation(); + const { getFieldDecorator, isFieldsTouched, resetFields } = form; + + return ( + + {isFieldsTouched() ? : null} +
+ {JSON.stringify(invoice)} + { + // + // {getFieldDecorator("line_desc", { + // initialValue: jobLine.line_desc + // })()} + // + // + // {getFieldDecorator("oem_partno", { + // initialValue: jobLine.oem_partno + // })()} + // + // + // {getFieldDecorator("part_type", { + // initialValue: jobLine.part_type + // })()} + // + // + // {getFieldDecorator("mod_lbr_ty", { + // initialValue: jobLine.mod_lbr_ty + // })()} + // + // + // {getFieldDecorator("op_code_desc", { + // initialValue: jobLine.op_code_desc + // })()} + // + // + // {getFieldDecorator("mod_lb_hrs", { + // initialValue: jobLine.mod_lb_hrs + // })()} + // + // + // {getFieldDecorator("act_price", { + // initialValue: jobLine.act_price + // })()} + // + } +
+
+ ); +} diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx b/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx new file mode 100644 index 000000000..5526af420 --- /dev/null +++ b/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx @@ -0,0 +1,114 @@ +import { Form, notification } from "antd"; +import React from "react"; +import { useMutation } from "react-apollo"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { + INSERT_NEW_JOB_LINE, + UPDATE_JOB_LINE +} from "../../graphql/jobs-lines.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectInvoiceEnterModal } from "../../redux/modals/modals.selectors"; +import InvoiceEnterModalComponent from "./invoice-enter-modal.component"; + +const mapStateToProps = createStructuredSelector({ + invoiceEnterModal: selectInvoiceEnterModal +}); +const mapDispatchToProps = dispatch => ({ + toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")) +}); + +function InvoiceEnterModalContainer({ + invoiceEnterModal, + toggleModalVisible, + form +}) { + const { t } = useTranslation(); + // const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); + // const [updateJobLine] = useMutation(UPDATE_JOB_LINE); + + const handleSubmit = e => { + e.preventDefault(); + + form.validateFieldsAndScroll((err, values) => { + if (err) { + notification["error"]({ + message: t("invoices.errors.validation"), + description: err.message + }); + } + if (!err) { + alert("Closing this modal."); + toggleModalVisible(); + // if (!jobLineEditModal.context.id) { + // insertJobLine({ + // variables: { + // lineInput: [{ jobid: jobLineEditModal.context.jobid, ...values }] + // } + // }) + // .then(r => { + // if (jobLineEditModal.actions.refetch) + // jobLineEditModal.actions.refetch(); + // toggleModalVisible(); + // notification["success"]({ + // message: t("joblines.successes.created") + // }); + // }) + // .catch(error => { + // notification["error"]({ + // message: t("joblines.errors.creating", { + // message: error.message + // }) + // }); + // }); + // } else { + // updateJobLine({ + // variables: { + // lineId: jobLineEditModal.context.id, + // line: values + // } + // }) + // .then(r => { + // notification["success"]({ + // message: t("joblines.successes.updated") + // }); + // }) + // .catch(error => { + // notification["success"]({ + // message: t("joblines.errors.updating", { + // message: error.message + // }) + // }); + // }); + // if (jobLineEditModal.actions.refetch) + // jobLineEditModal.actions.refetch(); + // toggleModalVisible(); + // } + } + }); + }; + + const handleCancel = () => { + toggleModalVisible(); + }; + + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)( + Form.create({ name: "InvoiceEnterModalContainer" })( + InvoiceEnterModalContainer + ) +); 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 0b11301b3..c60a18208 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -1,10 +1,12 @@ import { Button, Input, Table } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { alphaSort } from "../../utils/sorters"; -//import EditableCell from "./job-lines-cell.component"; import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; +import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container"; +import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container"; import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; export default function JobLinesComponent({ @@ -15,7 +17,8 @@ export default function JobLinesComponent({ selectedLines, setSelectedLines, partsOrderModalVisible, - jobId + jobId, + setJobLineEditContext }) { const [state, setState] = useState({ sortedInfo: {} @@ -44,7 +47,8 @@ export default function JobLinesComponent({ sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, ellipsis: true, - editable: true + editable: true, + width: "20%" }, { title: t("joblines.fields.oem_partno"), @@ -59,7 +63,7 @@ export default function JobLinesComponent({ state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order, ellipsis: true, editable: true, - width: "20%", + width: "10%", render: (text, record) => ( {record.oem_partno ? record.oem_partno : record.op_code_desc} @@ -75,7 +79,15 @@ export default function JobLinesComponent({ state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order, ellipsis: true, editable: true, - width: "10%" + width: "7%" + }, + { + title: t("joblines.fields.line_ind"), + dataIndex: "line_ind", + key: "line_ind", + sorter: (a, b) => alphaSort(a.line_ind, b.line_ind), + sortOrder: + state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order }, { title: t("joblines.fields.db_price"), @@ -85,7 +97,7 @@ export default function JobLinesComponent({ sortOrder: state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order, ellipsis: true, - width: "10%", + width: "8%", render: (text, record) => ( {record.db_price} ) @@ -98,7 +110,7 @@ export default function JobLinesComponent({ sortOrder: state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, ellipsis: true, - width: "10%", + width: "8%", render: (text, record) => ( {record.act_price} ) @@ -111,21 +123,39 @@ export default function JobLinesComponent({ sortOrder: state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order }, + { + title: t("joblines.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: + state.sortedInfo.columnKey === "status" && state.sortedInfo.order + }, { title: t("allocations.fields.employee"), dataIndex: "employee", key: "employee", - sorter: (a, b) => a.act_price - b.act_price, //TODO Fix employee sorting. + width: "10%", + sorter: (a, b) => + alphaSort( + a.allocations[0] && + a.allocations[0].employee.first_name + + a.allocations[0].employee.last_name, + b.allocations[0] && + b.allocations[0].employee.first_name + + b.allocations[0].employee.last_name + ), sortOrder: state.sortedInfo.columnKey === "employee" && state.sortedInfo.order, render: (text, record) => ( {record.allocations && record.allocations.length > 0 ? record.allocations.map(item => ( -
{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}
+ )) : null} ( - + ) } @@ -169,6 +208,7 @@ export default function JobLinesComponent({ @@ -185,23 +225,55 @@ export default function JobLinesComponent({ /> + + ); }} {...formItemLayout} loading={loading} - size='small' - pagination={{ position: "bottom", defaultPageSize: 50 }} + size="small" + expandedRowRender={record => ( +
+ {t("parts_orders.labels.orderhistory")} + {record.parts_order_lines.map(item => ( +
+ {`${item.parts_order.order_number || ""} from `} + + {item.parts_order.vendor.name || ""} + + {` on ${item.parts_order.order_date || ""}`} +
+ ))} +
+ )} + pagination={{ position: "top", defaultPageSize: 25 }} rowSelection={{ // selectedRowKeys: selectedLines, + onSelectAll: (selected, selectedRows, changeRows) => { + setSelectedLines(selectedRows); + }, onSelect: (record, selected, selectedRows, nativeEvent) => setSelectedLines(selectedRows) }} columns={columns.map(item => ({ ...item }))} - rowKey='id' + rowKey="id" dataSource={jobLines} onChange={handleTableChange} /> diff --git a/client/src/components/job-detail-lines/job-lines.container.jsx b/client/src/components/job-detail-lines/job-lines.container.jsx index 102905671..5ff515c19 100644 --- a/client/src/components/job-detail-lines/job-lines.container.jsx +++ b/client/src/components/job-detail-lines/job-lines.container.jsx @@ -5,9 +5,16 @@ import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries"; import AlertComponent from "../alert/alert.component"; import JobLinesComponent from "./job-lines.component"; -//export default Form.create({ name: "JobsDetailJobLines" })( - -export default function JobLinesContainer({ jobId }) { +import { connect } from "react-redux"; +import { setModalContext } from "../../redux/modals/modals.actions"; +const mapDispatchToProps = dispatch => ({ + setJobLineEditContext: context => + dispatch(setModalContext({ context: context, modal: "jobLineEdit" })) +}); +export default connect( + null, + mapDispatchToProps +)(function JobLinesContainer({ jobId, setJobLineEditContext }) { const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, { variables: { id: jobId }, fetchPolicy: "network-only" @@ -16,7 +23,8 @@ export default function JobLinesContainer({ jobId }) { const [searchText, setSearchText] = useState(""); const [selectedLines, setSelectedLines] = useState([]); const partsOrderModalVisible = useState(false); - if (error) return ; + + if (error) return ; return ( ); -} -//); +}); diff --git a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx new file mode 100644 index 000000000..aa87ea5f4 --- /dev/null +++ b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.component.jsx @@ -0,0 +1,69 @@ +import { Modal, Form, Input, InputNumber } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import ResetForm from "../form-items-formatted/reset-form-item.component"; + +export default function JobLinesUpsertModalComponent({ + visible, + jobLine, + handleOk, + handleCancel, + handleSubmit, + form +}) { + const { t } = useTranslation(); + const { getFieldDecorator, isFieldsTouched, resetFields } = form; + + return ( + + {isFieldsTouched() ? : null} +
+ + {getFieldDecorator("line_desc", { + initialValue: jobLine.line_desc + })()} + + + {getFieldDecorator("oem_partno", { + initialValue: jobLine.oem_partno + })()} + + + {getFieldDecorator("part_type", { + initialValue: jobLine.part_type + })()} + + + {getFieldDecorator("mod_lbr_ty", { + initialValue: jobLine.mod_lbr_ty + })()} + + + {getFieldDecorator("op_code_desc", { + initialValue: jobLine.op_code_desc + })()} + + + {getFieldDecorator("mod_lb_hrs", { + initialValue: jobLine.mod_lb_hrs + })()} + + + {getFieldDecorator("act_price", { + initialValue: jobLine.act_price + })()} + +
+
+ ); +} diff --git a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx new file mode 100644 index 000000000..fa1b732db --- /dev/null +++ b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx @@ -0,0 +1,110 @@ +import { Form, notification } from "antd"; +import React from "react"; +import { useMutation } from "react-apollo"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { + INSERT_NEW_JOB_LINE, + UPDATE_JOB_LINE +} from "../../graphql/jobs-lines.queries"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectJobLineEditModal } from "../../redux/modals/modals.selectors"; +import JobLinesUpdsertModal from "./job-lines-upsert-modal.component"; + +const mapStateToProps = createStructuredSelector({ + jobLineEditModal: selectJobLineEditModal +}); +const mapDispatchToProps = dispatch => ({ + toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")) +}); + +function JobLinesUpsertModalContainer({ + jobLineEditModal, + toggleModalVisible, + form +}) { + const { t } = useTranslation(); + const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); + const [updateJobLine] = useMutation(UPDATE_JOB_LINE); + + const handleSubmit = e => { + e.preventDefault(); + + form.validateFieldsAndScroll((err, values) => { + if (err) { + notification["error"]({ + message: t("joblines.errors.validation"), + description: err.message + }); + } + if (!err) { + if (!jobLineEditModal.context.id) { + insertJobLine({ + variables: { + lineInput: [{ jobid: jobLineEditModal.context.jobid, ...values }] + } + }) + .then(r => { + if (jobLineEditModal.actions.refetch) + jobLineEditModal.actions.refetch(); + toggleModalVisible(); + notification["success"]({ + message: t("joblines.successes.created") + }); + }) + .catch(error => { + notification["error"]({ + message: t("joblines.errors.creating", { + message: error.message + }) + }); + }); + } else { + updateJobLine({ + variables: { + lineId: jobLineEditModal.context.id, + line: values + } + }) + .then(r => { + notification["success"]({ + message: t("joblines.successes.updated") + }); + }) + .catch(error => { + notification["success"]({ + message: t("joblines.errors.updating", { + message: error.message + }) + }); + }); + if (jobLineEditModal.actions.refetch) + jobLineEditModal.actions.refetch(); + toggleModalVisible(); + } + } + }); + }; + + const handleCancel = () => { + toggleModalVisible(); + }; + + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)( + Form.create({ name: "JobsDetailPageContainer" })(JobLinesUpsertModalContainer) +); diff --git a/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx b/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx index e9c189aa0..5f0469cdb 100644 --- a/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx +++ b/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx @@ -54,12 +54,13 @@ export default withRouter(function JobsAvailableSupplementContainer({ //create upsert job let supp = estData.data.available_jobs_by_pk.est_data; delete supp.joblines; + //TODO How to update the estimate lines. delete supp.owner; delete supp.vehicle; if (!importOptions.overrideHeaders) { delete supp["ins_ea"]; - //Strip out the header options + //TODO Remove all required fields. } updateJob({ @@ -101,12 +102,11 @@ export default withRouter(function JobsAvailableSupplementContainer({ setSelectedJob(null); }; - if (error) return ; + if (error) return ; return ( + message={t("jobs.labels.creating_new_job")}> ({ + toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")), + setInvoiceEnterContext: context => + dispatch(setModalContext({ context: context, modal: "invoiceEnter" })) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(function JobsDetailPliComponent({ + toggleModalVisible, + setInvoiceEnterContext, + job +}) { + return ( +
+
{ + setInvoiceEnterContext({ + actions: { refetch: null }, + context: { + job + } + }); + }} + > + Enter Invoice +
+
+ ); +}); diff --git a/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx b/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx new file mode 100644 index 000000000..e6468ddcb --- /dev/null +++ b/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx @@ -0,0 +1,7 @@ +import React from "react"; +import JobsDetailPliComponent from "./jobs-detail-pli.component"; + +export default function JobsDetailPliContainer({ job }) { + console.log("job", job); + return ; +} diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index d44fbcc06..1b1ddb888 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,11 +1,11 @@ -import { Input, Table, Icon, Button } from "antd"; +import { Button, Icon, Input, Table } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; +import { Link, withRouter } from "react-router-dom"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter"; import { alphaSort } from "../../utils/sorters"; -import { withRouter } from "react-router-dom"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import StartChatButton from "../chat-open-button/chat-open-button.component"; export default withRouter(function JobsList({ searchTextState, @@ -78,13 +78,7 @@ export default withRouter(function JobsList({ return record.ownr_ph1 ? ( {record.ownr_ph1} - { - alert("SMSing will happen here."); - }} - /> + ) : ( t("general.labels.unknown") @@ -214,10 +208,10 @@ export default withRouter(function JobsList({ return (
{ setSearchText(e.target.value); }} @@ -226,10 +220,10 @@ export default withRouter(function JobsList({
); }} - size='small' + size="small" pagination={{ position: "top" }} columns={columns.map(item => ({ ...item }))} - rowKey='id' + rowKey="id" dataSource={jobs} rowSelection={{ selectedRowKeys: [selectedJob] }} onChange={handleTableChange} diff --git a/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx b/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx index 0d8a869b7..015801791 100644 --- a/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx +++ b/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx @@ -5,12 +5,20 @@ import { useTranslation } from "react-i18next"; import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries"; import NoteUpsertModalComponent from "./note-upsert-modal.component"; -export default function NoteUpsertModalContainer({ + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser +}); + +export default connect (mapStateToProps,null)( function NoteUpsertModalContainer({ jobId, visible, changeVisibility, refetch, - existingNote + existingNote,currentUser }) { const { t } = useTranslation(); const [insertNote] = useMutation(INSERT_NEW_NOTE); @@ -33,7 +41,7 @@ export default function NoteUpsertModalContainer({ insertNote({ variables: { noteInput: [ - { ...noteState, jobid: jobId, created_by: "patrick@bodyshop.app" } //TODO Fix the created by. + { ...noteState, jobid: jobId, created_by: currentUser.email } ] } }).then(r => { @@ -73,3 +81,4 @@ export default function NoteUpsertModalContainer({ /> ); } +); diff --git a/client/src/components/parts-order-modal/parts-order-modal.component.jsx b/client/src/components/parts-order-modal/parts-order-modal.component.jsx index 3c31da5ef..29cb8252b 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.component.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.component.jsx @@ -1,13 +1,15 @@ +import { AutoComplete, DatePicker, Icon, Input, List, Radio } from "antd"; import React, { useState } from "react"; -import { AutoComplete, Icon, DatePicker, Radio } from "antd"; import { useTranslation } from "react-i18next"; export default function PartsOrderModalComponent({ vendorList, state, - sendTypeState + sendTypeState, + orderLinesState }) { const [partsOrder, setPartsOrder] = state; const [sendType, setSendType] = sendTypeState; + const orderLines = orderLinesState[0]; const [vendorComplete, setVendorComplete] = useState(vendorList); const { t } = useTranslation(); @@ -22,8 +24,6 @@ export default function PartsOrderModalComponent({ }; const handleSelect = (value, option) => { - console.log("value", value); - console.log("option", option); setPartsOrder({ ...partsOrder, vendorid: option.key }); }; @@ -52,6 +52,33 @@ export default function PartsOrderModalComponent({ }} /> + {t("parts_orders.labels.inthisorder")} + + ( + + //TODO Editable table/adding line remarks to the order. + ]}> + { + // + // } + // title={{item.name.last}} + // description='Ant Design, a design language for background applications, is refined by Ant UED Team' + // /> + } +
{`${item.line_desc}${ + item.oem_partno ? " | " + item.oem_partno : "" + }`}
+
+ )} + /> + setSendType(e.target.value)}> 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 06cccf380..24c991a2b 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 @@ -1,50 +1,105 @@ import { Modal, notification } from "antd"; -import React, { useState } from "react"; -import { useQuery, useMutation } from "react-apollo"; +import React, { useState, useEffect } from "react"; +import { useMutation, useQuery } from "react-apollo"; +import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries"; import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries"; +import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries"; +import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries"; import { - selectCurrentUser, - selectBodyshop + selectBodyshop, + selectCurrentUser } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import PartsOrderModalComponent from "./parts-order-modal.component"; -import { useTranslation } from "react-i18next"; +import { + setEmailOptions, + toggleEmailOverlayVisible +} from "../../redux/email/email.actions"; +import PartsOrderEmailTemplate from "../../emails/parts-order/parts-order.email"; +import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, bodyshop: selectBodyshop }); +const mapDispatchToProps = dispatch => ({ + setEmailOptions: e => dispatch(setEmailOptions(e)), + toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible()) +}); export default connect( mapStateToProps, - null + mapDispatchToProps )(function PartsOrderModalContainer({ partsOrderModalVisible, linesToOrder, jobId, currentUser, - bodyshop + bodyshop, + refetch, + setEmailOptions, + toggleEmailOverlayVisible }) { - const [modalVisible, setModalVisible] = partsOrderModalVisible; - const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, { - fetchPolicy: "network-only", - skip: !modalVisible - }); const { t } = useTranslation(); - const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS); - const sendTypeState = useState("e"); + const [modalVisible, setModalVisible] = partsOrderModalVisible; + //set order lines to be a version of the incoming lines. + const orderLinesState = useState( + 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", + job_line_id: value.id, + status: bodyshop.md_order_statuses.default_ordered || "Ordered*" + }); + return acc; + }, []) + ); + const [orderLines, setOrderLinesState] = orderLinesState; + useEffect(() => { + if (modalVisible) + setOrderLinesState( + 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", + job_line_id: value.id, + status: bodyshop.md_order_statuses.default_ordered || "Ordered*" + }); + return acc; + }, []) + ); + }, [ + modalVisible, + setOrderLinesState, + linesToOrder, + bodyshop.md_order_statuses.default_ordered + ]); + + const sendTypeState = useState("e"); + const sendType = sendTypeState[0]; const partsOrderState = useState({ vendorid: null, jobid: jobId, user_email: currentUser.email }); - - console.log("sendTypeState[0]", sendTypeState[0]); const partsOrder = partsOrderState[0]; + + const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, { + fetchPolicy: "network-only", + skip: !modalVisible + }); + const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS); + const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS); + const handleOk = () => { insertPartOrder({ variables: { @@ -53,29 +108,58 @@ export default connect( ...partsOrder, status: bodyshop.md_order_statuses.default_ordered || "Ordered*", parts_order_lines: { - 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*" - }); - return acc; - }, []) + data: orderLines } } ] } }) .then(r => { - notification["success"]({ - message: t("parts_orders.successes.created") - }); - setModalVisible(false); + updateJobLines({ + variables: { + ids: linesToOrder.map(item => item.id), + status: bodyshop.md_order_statuses.default_ordered || "Ordered*" + } + }) + .then(response => { + notification["success"]({ + message: t("parts_orders.successes.created") + }); + if (refetch) refetch(); + setModalVisible(false); + + if (sendType === "e") { + //Show the email modal and set the data. + + //TODO Remove some of the options below. + setEmailOptions({ + messageOptions: { + from: { + name: "Kavia Autobdoy", + address: "noreply@bodyshop.app" + }, + to: "patrickwf@gmail.com", + replyTo: "snaptsoft@gmail.com" + }, + template: PartsOrderEmailTemplate, + queryConfig: [ + REPORT_QUERY_PARTS_ORDER_BY_PK, + { + variables: { + id: r.data.insert_parts_orders.returning[0].id + } + } + ] + }); + toggleEmailOverlayVisible(); + } + }) + .catch(error => { + notification["error"]({ + message: t("parts_orders.errors.creating"), + description: error.message + }); + }); }) .catch(error => { notification["error"]({ @@ -96,6 +180,7 @@ export default connect( vendorList={(data && data.vendors) || []} state={partsOrderState} sendTypeState={sendTypeState} + orderLinesState={orderLinesState} />
diff --git a/client/src/components/send-email-button/send-email-button.container.jsx b/client/src/components/send-email-button/send-email-button.container.jsx deleted file mode 100644 index 23be75fb3..000000000 --- a/client/src/components/send-email-button/send-email-button.container.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Button, Modal } from "antd"; -import axios from "axios"; -import React, { useState } from "react"; -import { useQuery } from "react-apollo"; -//Message options has the to & from details -//Query Config is what can get popped into UseQuery(QUERY_NAME, {variables: {vars}, fetchonly}) -//Template Which template should be used to send the email. -import ReactDOMServer from "react-dom/server"; -import { renderEmail } from "react-html-email"; -import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import SendEmailButtonComponent from "./send-email-button.component"; -export default function SendEmail({ - MessageOptions, - QueryConfig, - Template, - ...otherProps -}) { - const [modalVisible, setModalVisible] = useState(false); - const [emailConfig, setEmailConfig] = useState({ ...MessageOptions }); - const [gqlQuery, vars] = QueryConfig; - - const { loading, data } = useQuery(gqlQuery, { - ...vars, - fetchPolicy: "network-only", - skip: !modalVisible - }); - if (data && !emailConfig.html) { - console.log(ReactDOMServer.renderToStaticMarkup(