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 (
-
-
-
toggleChatVisible()}>X
-
-
- {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 ? (
-
- ) : (
-
- toggleChatVisible()}
- />
-
- )}
-
- );
-});
diff --git a/client/src/components/send-email-button/send-email-button.component.jsx b/client/src/components/email-overlay/email-overlay.component.jsx
similarity index 57%
rename from client/src/components/send-email-button/send-email-button.component.jsx
rename to client/src/components/email-overlay/email-overlay.component.jsx
index 86187363d..956ae7d69 100644
--- a/client/src/components/send-email-button/send-email-button.component.jsx
+++ b/client/src/components/email-overlay/email-overlay.component.jsx
@@ -4,41 +4,34 @@ import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
export default function SendEmailButtonComponent({
- emailConfig,
+ messageOptions,
handleConfigChange,
handleHtmlChange
}) {
return (
- THis is where the text editing will happen To
CC
Subject
{
- // You can store the "editor" and use when it is needed.
- console.log("Editor is ready to use!", editor);
- }}
+ data={messageOptions.html}
onChange={(event, editor) => {
- const data = editor.getData();
- console.log({ event, editor, data });
- handleHtmlChange(data);
+ handleHtmlChange(editor.getData());
}}
/>
diff --git a/client/src/components/email-overlay/email-overlay.container.jsx b/client/src/components/email-overlay/email-overlay.container.jsx
new file mode 100644
index 000000000..264452077
--- /dev/null
+++ b/client/src/components/email-overlay/email-overlay.container.jsx
@@ -0,0 +1,104 @@
+import { Button, Modal, notification } from "antd";
+import axios from "axios";
+import React, { useEffect, useState } from "react";
+import { useLazyQuery } from "react-apollo";
+import ReactDOMServer from "react-dom/server";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { toggleEmailOverlayVisible } from "../../redux/email/email.actions";
+import { selectEmailConfig, selectEmailVisible } from "../../redux/email/email.selectors.js";
+import LoadingSpinner from "../loading-spinner/loading-spinner.component";
+import EmailOverlayComponent from "./email-overlay.component";
+
+const mapStateToProps = createStructuredSelector({
+ modalVisible: selectEmailVisible,
+ emailConfig: selectEmailConfig
+});
+const mapDispatchToProps = dispatch => ({
+ toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(function SendEmail({ emailConfig, modalVisible, toggleEmailOverlayVisible }) {
+ const { t } = useTranslation();
+ const [messageOptions, setMessageOptions] = useState(
+ emailConfig.messageOptions
+ );
+ useEffect(() => {
+ setMessageOptions(emailConfig.messageOptions);
+ }, [setMessageOptions, emailConfig.messageOptions]);
+
+ const [executeQuery, { called, loading, data }] = useLazyQuery(
+ emailConfig.queryConfig[0],
+ {
+ ...emailConfig.queryConfig[1],
+ fetchPolicy: "network-only"
+ }
+ );
+
+ if (
+ emailConfig.queryConfig[0] &&
+ emailConfig.queryConfig[1] &&
+ modalVisible &&
+ !called
+ ) {
+ executeQuery();
+ }
+
+ if (data && !messageOptions.html && emailConfig.template) {
+ setMessageOptions({
+ ...messageOptions,
+ html: ReactDOMServer.renderToStaticMarkup(
+
+ )
+ //html: renderEmail( )
+ });
+ }
+
+ const handleOk = () => {
+ //sendEmail(messageOptions);
+ axios
+ .post("/sendemail", messageOptions)
+ .then(response => {
+ console.log(JSON.stringify(response));
+ notification["success"]({ message: t("emails.successes.sent") });
+ toggleEmailOverlayVisible();
+ })
+ .catch(error => {
+ console.log(JSON.stringify(error));
+ notification["error"]({
+ message: t("emails.errors.notsent", { message: error.message })
+ });
+ });
+ };
+ const handleConfigChange = event => {
+ const { name, value } = event.target;
+ setMessageOptions({ ...messageOptions, [name]: value });
+ };
+ const handleHtmlChange = text => {
+ setMessageOptions({ ...messageOptions, html: text });
+ };
+
+ return (
+
+ toggleEmailOverlayVisible()}>
+
+
+
+
+
+ toggleEmailOverlayVisible()}>Show
+
+ );
+});
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")}
- resetFields()}>
+ resetFields()}>
{t("general.actions.reset")}
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 ? (
-
-
+
+
) : 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}
+
+ // {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}
(
- {t("general.actions.edit")}
+ {
+ setJobLineEditContext({
+ actions: { refetch: refetch },
+ context: record
+ });
+ }}
+ >
+ {t("general.actions.edit")}
+
)
}
@@ -169,6 +208,7 @@ export default function JobLinesComponent({
@@ -185,23 +225,55 @@ export default function JobLinesComponent({
/>
0 ? false : true}
- onClick={() => setPartsModalVisible(true)}>
+ onClick={() => setPartsModalVisible(true)}
+ >
{t("parts.actions.order")}
+
+ {
+ setJobLineEditContext({
+ actions: { refetch: refetch },
+ context: { jobid: jobId }
+ });
+ }}
+ >
+ {t("joblines.actions.new")}
+
);
}}
{...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 (
refetch()}>
-
+
{
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( ));
- setEmailConfig({
- ...emailConfig,
- //html: ReactDOMServer.renderToStaticMarkup( )
- html: renderEmail( )
- });
- }
-
- const handleConfigChange = event => {
- const { name, value } = event.target;
- setEmailConfig({ ...emailConfig, [name]: value });
- };
-
- const handleHtmlChange = text => {
- setEmailConfig({ ...emailConfig, html: text });
- };
-
- const sendEmail = () => {
- axios.post("/sendemail", emailConfig).then(response => {
- alert(JSON.stringify(response));
- });
- };
- return (
-
- setModalVisible(false)}>
-
-
-
-
-
- setModalVisible(true)}>
- {otherProps.children}
-
-
- );
-}
diff --git a/client/src/emails/components/grid/grid.component.jsx b/client/src/emails/components/grid/grid.component.jsx
index ae632e913..b21f70e54 100644
--- a/client/src/emails/components/grid/grid.component.jsx
+++ b/client/src/emails/components/grid/grid.component.jsx
@@ -1,6 +1,6 @@
import React from "react";
-function Cell({ children }) {
+function Column({ children }) {
return {children} ;
}
@@ -8,7 +8,7 @@ function Row({ children }) {
return (
{React.Children.map(children, el => {
- if (el.type === Cell) return el;
+ if (el.type === Column) return el;
return {el} ;
})}
@@ -25,7 +25,7 @@ function Grid({ children }) {
if (el.type === Row) return el;
- if (el.type === Cell) {
+ if (el.type === Column) {
return {el} ;
}
@@ -41,6 +41,6 @@ function Grid({ children }) {
}
Grid.Row = Row;
-Grid.Cell = Cell;
+Grid.Column = Column;
export default Grid;
diff --git a/client/src/emails/components/header/header.component.jsx b/client/src/emails/components/header/header.component.jsx
new file mode 100644
index 000000000..8136aba6b
--- /dev/null
+++ b/client/src/emails/components/header/header.component.jsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+export default function Header({ bodyshop }) {
+ return (
+
+
+
+
+
+
+
+ {`${bodyshop.shopname}`}
+
+ {`${bodyshop.address1} ${bodyshop.address2} ${bodyshop.city} ${bodyshop.state} ${bodyshop.zip_post}`}
+
+
+
+
+ );
+}
diff --git a/client/src/emails/parts-order/parts-order.email.jsx b/client/src/emails/parts-order/parts-order.email.jsx
index 5121e9346..91c5b5e91 100644
--- a/client/src/emails/parts-order/parts-order.email.jsx
+++ b/client/src/emails/parts-order/parts-order.email.jsx
@@ -1,30 +1,53 @@
import React from "react";
-import { A, Box, Email, Item, Span } from "react-html-email";
+import styled from "styled-components";
+import Header from "../components/header/header.component";
+
+const D = styled.div`
+ table {
+ font-family: arial, sans-serif;
+ border-collapse: collapse;
+ width: 100%;
+ }
+
+ td,
+ th {
+ border: 1px solid #dddddd;
+ text-align: left;
+ padding: 8px;
+ }
+
+ tr:nth-child(even) {
+ background-color: #dddddd;
+ }
+`;
export default function PartsOrderEmail({ data }) {
- console.log("Data", data);
+ const order = data.parts_orders_by_pk;
return (
-
-
- -
-
- This is an example email made with:
-
- react-html-email
-
- .
-
-
- -
-
- This is an example email made with:
-
- react-html-email
-
- .
-
-
-
-
+
+
+
+
+ {`Deliver By: ${order.deliver_by}`}
+ {`Ordered By: ${order.user_email}`}
+
+
+
+
+ Line Description
+ Part #
+ Price
+ Line Remarks
+
+ {order.parts_order_lines.map(item => (
+
+ {item.line_desc}
+ {item.oem_partno}
+ {item.act_price}
+ {item.line_remarks}
+
+ ))}
+
+
);
}
diff --git a/client/src/graphql/allocations.queries.js b/client/src/graphql/allocations.queries.js
index 728f5534c..5c4d8e169 100644
--- a/client/src/graphql/allocations.queries.js
+++ b/client/src/graphql/allocations.queries.js
@@ -9,3 +9,13 @@ export const INSERT_ALLOCATION = gql`
}
}
`;
+
+export const DELETE_ALLOCATION = gql`
+ mutation DELETE_ALLOCATION($id: uuid!) {
+ delete_allocations(where: { id: { _eq: $id } }) {
+ returning {
+ id
+ }
+ }
+ }
+`;
diff --git a/client/src/graphql/jobs-lines.queries.js b/client/src/graphql/jobs-lines.queries.js
index 5efd5ccd0..197e4f6e6 100644
--- a/client/src/graphql/jobs-lines.queries.js
+++ b/client/src/graphql/jobs-lines.queries.js
@@ -18,6 +18,20 @@ export const GET_JOB_LINES_BY_PK = gql`
lbr_op
lbr_amt
op_code_desc
+ status
+ parts_order_lines {
+ id
+ parts_order {
+ id
+ order_number
+ order_date
+ user_email
+ vendor {
+ id
+ name
+ }
+ }
+ }
allocations {
id
hours
@@ -30,3 +44,30 @@ export const GET_JOB_LINES_BY_PK = gql`
}
}
`;
+
+export const UPDATE_JOB_LINE_STATUS = gql`
+ mutation UPDATE_JOB_LINE_STATUS($ids: [uuid!]!, $status: String!) {
+ update_joblines(where: { id: { _in: $ids } }, _set: { status: $status }) {
+ affected_rows
+ }
+ }
+`;
+export const INSERT_NEW_JOB_LINE = gql`
+ mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) {
+ insert_joblines(objects: $lineInput) {
+ returning {
+ id
+ }
+ }
+ }
+`;
+
+export const UPDATE_JOB_LINE = gql`
+ mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
+ update_joblines(where: { id: { _eq: $lineId } }, _set: $line) {
+ returning {
+ id
+ }
+ }
+ }
+`;
diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx
index bf04307a2..5de238e63 100644
--- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx
+++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx
@@ -1,18 +1,67 @@
import { Form, Icon, Tabs } from "antd";
-import React, { useContext } from "react";
+import React, { lazy, Suspense, useContext } from "react";
import { useTranslation } from "react-i18next";
-import { FaHardHat, FaInfo, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
+import {
+ FaHardHat,
+ FaInfo,
+ FaRegStickyNote,
+ FaShieldAlt
+} from "react-icons/fa";
import ResetForm from "../../components/form-items-formatted/reset-form-item.component";
-import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
-import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
-import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
-import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
-import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
-import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
-import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
-import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
-import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
+//import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
+//import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
+//import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
+//import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
+//import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
+//import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
+//import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
+//import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
+//import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
+//import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
+//import EnterInvoiceModalContainer from "../../components/invoice-enter-modal/invoice-enter-modal.container";
+import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import JobDetailFormContext from "./jobs-detail.page.context";
+import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail-pli.container";
+
+const JobsLinesContainer = lazy(() =>
+ import("../../components/job-detail-lines/job-lines.container")
+);
+const JobsDetailClaims = lazy(() =>
+ import("../../components/jobs-detail-claims/jobs-detail-claims.component")
+);
+const JobsDetailDatesComponent = lazy(() =>
+ import("../../components/jobs-detail-dates/jobs-detail-dates.component")
+);
+const JobsDetailFinancials = lazy(() =>
+ import(
+ "../../components/jobs-detail-financial/jobs-detail-financial.component"
+ )
+);
+const JobsDetailHeader = lazy(() =>
+ import("../../components/jobs-detail-header/jobs-detail-header.component")
+);
+const JobsDetailInsurance = lazy(() =>
+ import(
+ "../../components/jobs-detail-insurance/jobs-detail-insurance.component"
+ )
+);
+const JobsDocumentsContainer = lazy(() =>
+ import("../../components/jobs-documents/jobs-documents.container")
+);
+const JobNotesContainer = lazy(() =>
+ import("../../components/jobs-notes/jobs-notes.container")
+);
+const ScheduleJobModalContainer = lazy(() =>
+ import("../../components/schedule-job-modal/schedule-job-modal.container")
+);
+const JobLineUpsertModalContainer = lazy(() =>
+ import(
+ "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"
+ )
+);
+const EnterInvoiceModalContainer = lazy(() =>
+ import("../../components/invoice-enter-modal/invoice-enter-modal.container")
+);
export default function JobsDetailPage({
job,
@@ -39,13 +88,18 @@ export default function JobsDetailPage({
};
return (
-
+ }
+ >
+
+
+
-
+
);
}
diff --git a/client/src/pages/jobs/jobs.page.jsx b/client/src/pages/jobs/jobs.page.jsx
index f8339a99e..41b9cfa15 100644
--- a/client/src/pages/jobs/jobs.page.jsx
+++ b/client/src/pages/jobs/jobs.page.jsx
@@ -34,10 +34,8 @@ export default connect(
const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null);
const searchTextState = useState("");
const searchText = searchTextState[0];
- if (error) return ;
- //TODO Implement pagination for this.
+ if (error) return ;
- console.log(typeof searchText);
return (
-
- Send an Email in new Window
-
+ {
+ //
+ // Send an Email in new Window
+ //
+ }
);
}
diff --git a/client/src/pages/manage/manage.page.jsx b/client/src/pages/manage/manage.page.jsx
index d194eef59..a25bd34bc 100644
--- a/client/src/pages/manage/manage.page.jsx
+++ b/client/src/pages/manage/manage.page.jsx
@@ -8,6 +8,7 @@ import FooterComponent from "../../components/footer/footer.component";
import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import "./manage.page.styles.scss";
+import Test from "../../components/_test/test.component";
const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container")
@@ -24,7 +25,7 @@ const JobsAvailablePage = lazy(() =>
import("../jobs-available/jobs-available.page.container")
);
const ChatWindowContainer = lazy(() =>
- import("../../components/chat-window/chat-window.container")
+ import("../../components/chat-overlay/chat-overlay.container")
);
const ScheduleContainer = lazy(() =>
import("../schedule/schedule.page.container")
@@ -44,6 +45,10 @@ const ShopVendorPageContainer = lazy(() =>
import("../shop-vendor/shop-vendor.page.container")
);
+const EmailOverlayContainer = lazy(() =>
+ import("../../components/email-overlay/email-overlay.container.jsx")
+);
+
const { Header, Content, Footer } = Layout;
export default function Manage({ match }) {
@@ -60,17 +65,20 @@ export default function Manage({ match }) {
+ className="content-container"
+ style={{ padding: "0em 4em 4em" }}
+ >
- }>
+ }
+ >
+ DELETE THIS
+
+
-
-
-
-
+
);
diff --git a/client/src/redux/email/email.actions.js b/client/src/redux/email/email.actions.js
new file mode 100644
index 000000000..1aaf9a097
--- /dev/null
+++ b/client/src/redux/email/email.actions.js
@@ -0,0 +1,24 @@
+import EmailActionTypes from "./email.types";
+
+export const toggleEmailOverlayVisible = () => ({
+ type: EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE
+});
+
+export const setEmailOptions = options => ({
+ type: EmailActionTypes.SET_EMAIL_OPTIONS,
+ payload: options
+});
+
+export const sendEmail = email => ({
+ type: EmailActionTypes.SEND_EMAIL,
+ payload: email
+});
+
+export const sendEmailSuccess = options => ({
+ type: EmailActionTypes.SEND_EMAIL_SUCCESS
+});
+
+export const sendEmailFailure = error => ({
+ type: EmailActionTypes.SEND_EMAIL_FAILURE,
+ payload: error
+});
diff --git a/client/src/redux/email/email.reducer.js b/client/src/redux/email/email.reducer.js
new file mode 100644
index 000000000..7a39c94cc
--- /dev/null
+++ b/client/src/redux/email/email.reducer.js
@@ -0,0 +1,35 @@
+import EmailActionTypes from "./email.types";
+
+const INITIAL_STATE = {
+ emailConfig: {
+ messageOptions: {
+ from: { name: "ShopName", address: "noreply@bodyshop.app" },
+ to: null,
+ replyTo: null
+ },
+ template: null,
+ queryConfig: [null, { variables: null }]
+ },
+
+ visible: false,
+ error: null
+};
+
+const emailReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE:
+ return {
+ ...state,
+ visible: !state.visible
+ };
+ case EmailActionTypes.SET_EMAIL_OPTIONS:
+ return {
+ ...state,
+ emailConfig: { ...action.payload }
+ };
+ default:
+ return state;
+ }
+};
+
+export default emailReducer;
diff --git a/client/src/redux/email/email.sagas.js b/client/src/redux/email/email.sagas.js
new file mode 100644
index 000000000..a7b562d48
--- /dev/null
+++ b/client/src/redux/email/email.sagas.js
@@ -0,0 +1,51 @@
+import { all, call, put, takeLatest } from "redux-saga/effects";
+import { sendEmailFailure, sendEmailSuccess } from "./email.actions";
+import EmailActionTypes from "./email.types";
+import axios from "axios";
+
+export function* onSendEmail() {
+ yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail);
+}
+export function* sendEmail(payload) {
+ try {
+ console.log("Sending thta email", payload);
+ axios.post("/sendemail", payload).then(response => {
+ console.log(JSON.stringify(response));
+ put(sendEmailSuccess());
+ });
+ } catch (error) {
+ console.log("Error in sendEmail saga.");
+ yield put(sendEmailFailure(error.message));
+ }
+}
+
+export function* onSendEmailSuccess() {
+ yield takeLatest(EmailActionTypes.SEND_EMAIL_SUCCESS, sendEmailSuccessSaga);
+}
+export function* sendEmailSuccessSaga() {
+ try {
+ console.log("Send email success.");
+ } catch (error) {
+ console.log("Error in sendEmailSuccess saga.");
+ yield put(sendEmailFailure(error.message));
+ }
+}
+
+export function* onSendEmailFailure() {
+ yield takeLatest(EmailActionTypes.SEND_EMAIL_FAILURE, sendEmailFailureSaga);
+}
+export function* sendEmailFailureSaga(error) {
+ try {
+ yield console.log(error);
+ } catch (error) {
+ console.log("Error in sendEmailFailure saga.", error.message);
+ }
+}
+
+export function* emailSagas() {
+ yield all([
+ call(onSendEmail),
+ call(onSendEmailFailure),
+ call(onSendEmailSuccess)
+ ]);
+}
diff --git a/client/src/redux/email/email.selectors.js b/client/src/redux/email/email.selectors.js
new file mode 100644
index 000000000..3a0299aa4
--- /dev/null
+++ b/client/src/redux/email/email.selectors.js
@@ -0,0 +1,25 @@
+import { createSelector } from "reselect";
+
+const selectEmail = state => state.email;
+const selectEmailConfigMessageOptions = state =>
+ state.email.emailConfig.messageOptions;
+const selectEmailConfigTemplate = state => state.email.emailConfig.template;
+const selectEmailConfigQuery = state => state.email.emailConfig.queryConfig;
+
+export const selectEmailVisible = createSelector(
+ [selectEmail],
+ email => email.visible
+);
+
+export const selectEmailConfig = createSelector(
+ [
+ selectEmailConfigMessageOptions,
+ selectEmailConfigTemplate,
+ selectEmailConfigQuery
+ ],
+ (messageOptions, template, queryConfig) => ({
+ messageOptions,
+ template,
+ queryConfig
+ })
+);
diff --git a/client/src/redux/email/email.types.js b/client/src/redux/email/email.types.js
new file mode 100644
index 000000000..b7e1ef876
--- /dev/null
+++ b/client/src/redux/email/email.types.js
@@ -0,0 +1,8 @@
+const EmailActionTypes = {
+ TOGGLE_EMAIL_OVERLAY_VISIBLE: "TOGGLE_EMAIL_OVERLAY_VISIBLE",
+ SET_EMAIL_OPTIONS: "SET_EMAIL_OPTIONS",
+ SEND_EMAIL: "SEND_EMAIL",
+ SEND_EMAIL_SUCCESS: "SEND_EMAIL_SUCCESS",
+ SEND_EMAIL_FAILURE: "SEND_EMAIL_FAILURE"
+};
+export default EmailActionTypes;
diff --git a/client/src/redux/messaging/messaging.actions.js b/client/src/redux/messaging/messaging.actions.js
index c42e19aa1..b89ab0ebb 100644
--- a/client/src/redux/messaging/messaging.actions.js
+++ b/client/src/redux/messaging/messaging.actions.js
@@ -1,7 +1,21 @@
-import MessagingActionTypes from './messaging.types'
+import MessagingActionTypes from "./messaging.types";
export const toggleChatVisible = () => ({
- type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
+ type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE
//payload: user
});
+export const toggleConversationVisible = conversationId => ({
+ type: MessagingActionTypes.TOGGLE_CONVERSATION_VISIBLE,
+ payload: conversationId
+});
+
+export const openConversation = phone => ({
+ type: MessagingActionTypes.OPEN_CONVERSATION,
+ payload: phone
+});
+
+export const closeConversation = phone => ({
+ type: MessagingActionTypes.CLOSE_CONVERSATION,
+ payload: phone
+});
diff --git a/client/src/redux/messaging/messaging.reducer.js b/client/src/redux/messaging/messaging.reducer.js
index 6b28f650a..f9acdf08e 100644
--- a/client/src/redux/messaging/messaging.reducer.js
+++ b/client/src/redux/messaging/messaging.reducer.js
@@ -1,7 +1,11 @@
import MessagingActionTypes from "./messaging.types";
const INITIAL_STATE = {
- visible: false
+ visible: false,
+ conversations: [
+ { phone: "6049992002", open: false },
+ { phone: "6049992991", open: false }
+ ]
};
const messagingReducer = (state = INITIAL_STATE, action) => {
@@ -16,6 +20,36 @@ const messagingReducer = (state = INITIAL_STATE, action) => {
...state,
visible: true
};
+ case MessagingActionTypes.OPEN_CONVERSATION:
+ if (state.conversations.find(c => c.phone === action.payload))
+ return {
+ ...state,
+ conversations: state.conversations.map(c =>
+ c.phone === action.payload ? { ...c, open: true } : c
+ )
+ };
+ else
+ return {
+ ...state,
+ conversations: [
+ ...state.conversations,
+ { phone: action.payload, open: true }
+ ]
+ };
+ case MessagingActionTypes.CLOSE_CONVERSATION:
+ return {
+ ...state,
+ conversations: state.conversations.filter(
+ c => c.phone !== action.payload
+ )
+ };
+ case MessagingActionTypes.TOGGLE_CONVERSATION_VISIBLE:
+ return {
+ ...state,
+ conversations: state.conversations.map(c =>
+ c.phone === action.payload ? { ...c, open: !c.open } : c
+ )
+ };
default:
return state;
}
diff --git a/client/src/redux/messaging/messaging.selectors.js b/client/src/redux/messaging/messaging.selectors.js
index 65e17529d..b47f6743c 100644
--- a/client/src/redux/messaging/messaging.selectors.js
+++ b/client/src/redux/messaging/messaging.selectors.js
@@ -6,3 +6,8 @@ export const selectChatVisible = createSelector(
[selectMessaging],
messaging => messaging.visible
);
+
+export const selectConversations = createSelector(
+ [selectMessaging],
+ messaging => messaging.conversations
+);
diff --git a/client/src/redux/messaging/messaging.types.js b/client/src/redux/messaging/messaging.types.js
index 6beaf6102..462e7efb3 100644
--- a/client/src/redux/messaging/messaging.types.js
+++ b/client/src/redux/messaging/messaging.types.js
@@ -1,5 +1,9 @@
const MessagingActionTypes = {
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
- SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE"
+ SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE",
+ OPEN_CONVERSATION: "OPEN_CONVERSATION",
+ CLOSE_CONVERSATION: "CLOSE_CONVERSATION",
+ TOGGLE_CONVERSATION_VISIBLE: "TOGGLE_CONVERSATION_VISIBLE",
+ SEND_MESSAGE: "SEND_MESSAGE"
};
export default MessagingActionTypes;
diff --git a/client/src/redux/modals/modals.actions.js b/client/src/redux/modals/modals.actions.js
new file mode 100644
index 000000000..b11db8881
--- /dev/null
+++ b/client/src/redux/modals/modals.actions.js
@@ -0,0 +1,12 @@
+import ModalsActionTypes from "./modals.types";
+
+export const toggleModalVisible = modalName => ({
+ type: ModalsActionTypes.TOGGLE_MODAL_VISIBLE,
+ payload: modalName
+});
+
+//Modal Context: {context (context object), modal(name of modal)}
+export const setModalContext = modalContext => ({
+ type: ModalsActionTypes.SET_MODAL_CONTEXT,
+ payload: modalContext
+});
diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js
new file mode 100644
index 000000000..7e79e844b
--- /dev/null
+++ b/client/src/redux/modals/modals.reducer.js
@@ -0,0 +1,40 @@
+import ModalsActionTypes from "./modals.types";
+
+const baseModal = {
+ visible: false,
+ context: {},
+ actions: {
+ refetch: null
+ }
+};
+
+const INITIAL_STATE = {
+ jobLineEdit: { ...baseModal },
+ invoiceEnter: { ...baseModal }
+};
+
+const modalsReducer = (state = INITIAL_STATE, action) => {
+ switch (action.type) {
+ case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
+ return {
+ ...state,
+ [action.payload]: {
+ ...state[action.payload],
+ visible: !state[action.payload].visible
+ }
+ };
+ case ModalsActionTypes.SET_MODAL_CONTEXT:
+ return {
+ ...state,
+ [action.payload.modal]: {
+ ...state[action.payload.modal],
+ ...action.payload.context,
+ visible: true
+ }
+ };
+ default:
+ return state;
+ }
+};
+
+export default modalsReducer;
diff --git a/client/src/redux/modals/modals.sagas.js b/client/src/redux/modals/modals.sagas.js
new file mode 100644
index 000000000..7cf750062
--- /dev/null
+++ b/client/src/redux/modals/modals.sagas.js
@@ -0,0 +1,24 @@
+import { all } from "redux-saga/effects";
+
+// export function* onSendEmail() {
+// yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail);
+// }
+// export function* sendEmail(payload) {
+// try {
+// console.log("Sending thta email", payload);
+// axios.post("/sendemail", payload).then(response => {
+// console.log(JSON.stringify(response));
+// put(sendEmailSuccess());
+// });
+// } catch (error) {
+// console.log("Error in sendEmail saga.");
+// yield put(sendEmailFailure(error.message));
+// }
+// }
+
+
+export function* modalsSagas() {
+ yield all([
+ //call(onSendEmail),
+ ]);
+}
diff --git a/client/src/redux/modals/modals.selectors.js b/client/src/redux/modals/modals.selectors.js
new file mode 100644
index 000000000..7634faf2b
--- /dev/null
+++ b/client/src/redux/modals/modals.selectors.js
@@ -0,0 +1,14 @@
+import { createSelector } from "reselect";
+
+const selectModals = state => state.modals;
+
+export const selectJobLineEditModal = createSelector(
+ [selectModals],
+ modals => modals.jobLineEdit
+);
+
+export const selectInvoiceEnterModal = createSelector(
+ [selectModals],
+ modals => modals.invoiceEnter
+);
+
diff --git a/client/src/redux/modals/modals.types.js b/client/src/redux/modals/modals.types.js
new file mode 100644
index 000000000..01ea4c793
--- /dev/null
+++ b/client/src/redux/modals/modals.types.js
@@ -0,0 +1,5 @@
+const ModalActionTypes = {
+ TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE",
+ SET_MODAL_CONTEXT: "SET_JOBLINEEDIT_CONTEXT"
+};
+export default ModalActionTypes;
diff --git a/client/src/redux/root.reducer.js b/client/src/redux/root.reducer.js
index 47720506d..091bd39fe 100644
--- a/client/src/redux/root.reducer.js
+++ b/client/src/redux/root.reducer.js
@@ -4,23 +4,20 @@ import storage from "redux-persist/lib/storage";
import userReducer from "./user/user.reducer";
import messagingReducer from "./messaging/messaging.reducer";
-// import cartReducer from './cart/cart.reducer';
-// import directoryReducer from './directory/directory.reducer';
-// import shopReducer from './shop/shop.reducer';
-
+import emailReducer from "./email/email.reducer";
+import modalsReducer from './modals/modals.reducer'
const persistConfig = {
key: "root",
storage,
- //whitelist: ["cart"]
- blacklist: ["user"]
+ //whitelist: ["user"]
+ blacklist: ["user", "email", "messaging", "modals"]
};
const rootReducer = combineReducers({
user: userReducer,
- messaging: messagingReducer
- // cart: cartReducer,
- // directory: directoryReducer,
- // shop: shopReducer
+ messaging: messagingReducer,
+ email: emailReducer,
+ modals: modalsReducer
});
export default persistReducer(persistConfig, rootReducer);
diff --git a/client/src/redux/root.saga.js b/client/src/redux/root.saga.js
index 532b2e050..ce288b568 100644
--- a/client/src/redux/root.saga.js
+++ b/client/src/redux/root.saga.js
@@ -1,12 +1,10 @@
import { all, call } from "redux-saga/effects";
-//List of all Sagas
-// import { shopSagas } from "./shop/shop.sagas";
import { userSagas } from "./user/user.sagas";
import { messagingSagas } from "./messaging/messaging.sagas";
-//import { cartSagas } from "./cart/cart.sagas";
-
+import { emailSagas } from "./email/email.sagas";
+import { modalsSagas } from "./modals/modals.sagas";
export default function* rootSaga() {
- //All starts all the Sagas concurrently.
- yield all([call(userSagas), call(messagingSagas)]);
+ yield all([call(userSagas), call(messagingSagas), call(emailSagas),
+ call(modalsSagas)]);
}
diff --git a/client/src/redux/store.js b/client/src/redux/store.js
index 736d68033..7a5034e60 100644
--- a/client/src/redux/store.js
+++ b/client/src/redux/store.js
@@ -1,6 +1,6 @@
import { createStore, applyMiddleware } from "redux";
import { persistStore } from "redux-persist";
-import logger from "redux-logger";
+import { createLogger } from "redux-logger";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./root.reducer";
import rootSaga from "./root.saga";
@@ -8,7 +8,7 @@ import rootSaga from "./root.saga";
const sagaMiddleWare = createSagaMiddleware();
const middlewares = [sagaMiddleWare];
if (process.env.NODE_ENV === "development") {
- middlewares.push(logger);
+ middlewares.push(createLogger({ collapsed: true, diff: true }));
}
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 70a7a1959..abee6521b 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -4,8 +4,17 @@
"actions": {
"assign": "Assign"
},
+ "errors": {
+ "deleting": "Error encountered while deleting allocation. {{message}}",
+ "saving": "Error while allocating. {{message}}",
+ "validation": "Please ensure all fields are entered correctly. "
+ },
"fields": {
"employee": "Allocated To"
+ },
+ "successes": {
+ "deleted": "Allocation deleted successfully.",
+ "save": "Allocated successfully. "
}
},
"appointments": {
@@ -67,6 +76,14 @@
"insert": "Uploaded document successfully. "
}
},
+ "emails": {
+ "errors": {
+ "notsent": "Email not sent. Error encountered while sending {{message}}"
+ },
+ "successes": {
+ "sent": "Email sent successfully."
+ }
+ },
"employees": {
"actions": {
"new": "New Employee"
@@ -128,14 +145,33 @@
}
},
"joblines": {
+ "actions": {
+ "new": "New Line"
+ },
+ "errors": {
+ "creating": "Error encountered while creating job line. {{message}}",
+ "updating": "Error encountered updating job line. {{message}}"
+ },
"fields": {
"act_price": "Actual Price",
"db_price": "Database Price",
"line_desc": "Line Description",
+ "line_ind": "S#",
"mod_lb_hrs": "Labor Hours",
+ "mod_lbr_ty": "Labor Type",
"oem_partno": "OEM Part #",
+ "op_code_desc": "Operation Code Description",
"part_type": "Part Type",
+ "status": "Status",
"unq_seq": "Seq #"
+ },
+ "labels": {
+ "edit": "Edit Line",
+ "new": "New Line"
+ },
+ "successes": {
+ "created": "Job line created successfully.",
+ "updated": "Job line updated successfully."
}
},
"jobs": {
@@ -323,6 +359,12 @@
"shops": "My Shops"
}
},
+ "messaging": {
+ "labels": {
+ "messaging": "Messaging",
+ "typeamessage": "Send a message..."
+ }
+ },
"notes": {
"actions": {
"actions": "Actions",
@@ -385,10 +427,13 @@
"creating": "Error encountered when creating parts order. "
},
"fields": {
- "deliver_by": "Deliver By"
+ "deliver_by": "Deliver By",
+ "lineremarks": "Line Remarks"
},
"labels": {
"email": "Send by Email",
+ "inthisorder": "Parts in this Order",
+ "orderhistory": "Order History",
"print": "Show Printed Form"
},
"successes": {
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 270442c45..b5a22f871 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -4,8 +4,17 @@
"actions": {
"assign": "Asignar"
},
+ "errors": {
+ "deleting": "",
+ "saving": "",
+ "validation": ""
+ },
"fields": {
"employee": "Asignado a"
+ },
+ "successes": {
+ "deleted": "",
+ "save": ""
}
},
"appointments": {
@@ -67,6 +76,14 @@
"insert": "Documento cargado con éxito."
}
},
+ "emails": {
+ "errors": {
+ "notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}"
+ },
+ "successes": {
+ "sent": "Correo electrónico enviado con éxito."
+ }
+ },
"employees": {
"actions": {
"new": "Nuevo empleado"
@@ -128,14 +145,33 @@
}
},
"joblines": {
+ "actions": {
+ "new": ""
+ },
+ "errors": {
+ "creating": "",
+ "updating": ""
+ },
"fields": {
"act_price": "Precio actual",
"db_price": "Precio de base de datos",
"line_desc": "Descripción de línea",
+ "line_ind": "S#",
"mod_lb_hrs": "Horas laborales",
+ "mod_lbr_ty": "Tipo de trabajo",
"oem_partno": "OEM parte #",
+ "op_code_desc": "",
"part_type": "Tipo de parte",
+ "status": "Estado",
"unq_seq": "Seq #"
+ },
+ "labels": {
+ "edit": "Línea de edición",
+ "new": "Nueva línea"
+ },
+ "successes": {
+ "created": "",
+ "updated": ""
}
},
"jobs": {
@@ -323,6 +359,12 @@
"shops": "Mis tiendas"
}
},
+ "messaging": {
+ "labels": {
+ "messaging": "Mensajería",
+ "typeamessage": "Enviar un mensaje..."
+ }
+ },
"notes": {
"actions": {
"actions": "Comportamiento",
@@ -385,10 +427,13 @@
"creating": "Se encontró un error al crear el pedido de piezas."
},
"fields": {
- "deliver_by": "Entregado por"
+ "deliver_by": "Entregado por",
+ "lineremarks": "Comentarios de línea"
},
"labels": {
"email": "Enviar por correo electrónico",
+ "inthisorder": "Partes en este pedido",
+ "orderhistory": "Historial de pedidos",
"print": "Mostrar formulario impreso"
},
"successes": {
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index e64421219..df942272d 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -4,8 +4,17 @@
"actions": {
"assign": "Attribuer"
},
+ "errors": {
+ "deleting": "",
+ "saving": "",
+ "validation": ""
+ },
"fields": {
"employee": "Alloué à"
+ },
+ "successes": {
+ "deleted": "",
+ "save": ""
}
},
"appointments": {
@@ -67,6 +76,14 @@
"insert": "Document téléchargé avec succès."
}
},
+ "emails": {
+ "errors": {
+ "notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}"
+ },
+ "successes": {
+ "sent": "E-mail envoyé avec succès."
+ }
+ },
"employees": {
"actions": {
"new": "Nouvel employé"
@@ -128,14 +145,33 @@
}
},
"joblines": {
+ "actions": {
+ "new": ""
+ },
+ "errors": {
+ "creating": "",
+ "updating": ""
+ },
"fields": {
"act_price": "Prix actuel",
"db_price": "Prix de la base de données",
"line_desc": "Description de la ligne",
+ "line_ind": "S#",
"mod_lb_hrs": "Heures de travail",
+ "mod_lbr_ty": "Type de travail",
"oem_partno": "Pièce OEM #",
+ "op_code_desc": "",
"part_type": "Type de pièce",
+ "status": "Statut",
"unq_seq": "Seq #"
+ },
+ "labels": {
+ "edit": "Ligne d'édition",
+ "new": "Nouvelle ligne"
+ },
+ "successes": {
+ "created": "",
+ "updated": ""
}
},
"jobs": {
@@ -323,6 +359,12 @@
"shops": "Mes boutiques"
}
},
+ "messaging": {
+ "labels": {
+ "messaging": "Messagerie",
+ "typeamessage": "Envoyer un message..."
+ }
+ },
"notes": {
"actions": {
"actions": "actes",
@@ -385,10 +427,13 @@
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
},
"fields": {
- "deliver_by": "Livrer par"
+ "deliver_by": "Livrer par",
+ "lineremarks": "Remarques sur la ligne"
},
"labels": {
"email": "Envoyé par email",
+ "inthisorder": "Pièces dans cette commande",
+ "orderhistory": "Historique des commandes",
"print": "Afficher le formulaire imprimé"
},
"successes": {
diff --git a/client/src/utils/sorters.js b/client/src/utils/sorters.js
index e0adffbf6..7064167d7 100644
--- a/client/src/utils/sorters.js
+++ b/client/src/utils/sorters.js
@@ -2,10 +2,7 @@ export function alphaSort(a, b) {
let A;
let B;
A = a ? a.toLowerCase() : "";
-
B = b ? b.toLowerCase() : "";
- console.log("Objects", A, B, A < B, A > B);
-
if (A < B)
//sort string ascending
return -1;
diff --git a/hasura/migrations/1582224710136_alter_table_public_joblines_add_column_status/down.yaml b/hasura/migrations/1582224710136_alter_table_public_joblines_add_column_status/down.yaml
new file mode 100644
index 000000000..2c4cd87c3
--- /dev/null
+++ b/hasura/migrations/1582224710136_alter_table_public_joblines_add_column_status/down.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."joblines" DROP COLUMN "status";
+ type: run_sql
diff --git a/hasura/migrations/1582224710136_alter_table_public_joblines_add_column_status/up.yaml b/hasura/migrations/1582224710136_alter_table_public_joblines_add_column_status/up.yaml
new file mode 100644
index 000000000..428023a4c
--- /dev/null
+++ b/hasura/migrations/1582224710136_alter_table_public_joblines_add_column_status/up.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."joblines" ADD COLUMN "status" text NULL;
+ type: run_sql
diff --git a/hasura/migrations/1582224725146_update_permission_user_public_table_joblines/down.yaml b/hasura/migrations/1582224725146_update_permission_user_public_table_joblines/down.yaml
new file mode 100644
index 000000000..3d0722715
--- /dev/null
+++ b/hasura/migrations/1582224725146_update_permission_user_public_table_joblines/down.yaml
@@ -0,0 +1,75 @@
+- args:
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: drop_insert_permission
+- args:
+ permission:
+ check:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ columns:
+ - id
+ - created_at
+ - updated_at
+ - jobid
+ - unq_seq
+ - line_ind
+ - line_desc
+ - part_type
+ - oem_partno
+ - est_seq
+ - db_ref
+ - line_ref
+ - tax_part
+ - db_price
+ - act_price
+ - part_qty
+ - alt_partno
+ - mod_lbr_ty
+ - db_hrs
+ - mod_lb_hrs
+ - lbr_op
+ - lbr_amt
+ - glass_flag
+ - price_inc
+ - alt_part_i
+ - price_j
+ - cert_part
+ - alt_co_id
+ - alt_overrd
+ - alt_partm
+ - prt_dsmk_p
+ - prt_dsmk_m
+ - lbr_inc
+ - lbr_hrs_j
+ - lbr_typ_j
+ - lbr_op_j
+ - paint_stg
+ - paint_tone
+ - lbr_tax
+ - misc_amt
+ - misc_sublt
+ - misc_tax
+ - bett_type
+ - bett_pctg
+ - bett_amt
+ - bett_tax
+ - op_code_desc
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: create_insert_permission
diff --git a/hasura/migrations/1582224725146_update_permission_user_public_table_joblines/up.yaml b/hasura/migrations/1582224725146_update_permission_user_public_table_joblines/up.yaml
new file mode 100644
index 000000000..df8b59464
--- /dev/null
+++ b/hasura/migrations/1582224725146_update_permission_user_public_table_joblines/up.yaml
@@ -0,0 +1,76 @@
+- args:
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: drop_insert_permission
+- args:
+ permission:
+ check:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ columns:
+ - id
+ - created_at
+ - updated_at
+ - jobid
+ - unq_seq
+ - line_ind
+ - line_desc
+ - part_type
+ - oem_partno
+ - est_seq
+ - db_ref
+ - line_ref
+ - tax_part
+ - db_price
+ - act_price
+ - part_qty
+ - alt_partno
+ - mod_lbr_ty
+ - db_hrs
+ - mod_lb_hrs
+ - lbr_op
+ - lbr_amt
+ - glass_flag
+ - price_inc
+ - alt_part_i
+ - price_j
+ - cert_part
+ - alt_co_id
+ - alt_overrd
+ - alt_partm
+ - prt_dsmk_p
+ - prt_dsmk_m
+ - lbr_inc
+ - lbr_hrs_j
+ - lbr_typ_j
+ - lbr_op_j
+ - paint_stg
+ - paint_tone
+ - lbr_tax
+ - misc_amt
+ - misc_sublt
+ - misc_tax
+ - bett_type
+ - bett_pctg
+ - bett_amt
+ - bett_tax
+ - op_code_desc
+ - status
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: create_insert_permission
diff --git a/hasura/migrations/1582224730652_update_permission_user_public_table_joblines/down.yaml b/hasura/migrations/1582224730652_update_permission_user_public_table_joblines/down.yaml
new file mode 100644
index 000000000..32c4e42a2
--- /dev/null
+++ b/hasura/migrations/1582224730652_update_permission_user_public_table_joblines/down.yaml
@@ -0,0 +1,73 @@
+- args:
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: drop_select_permission
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - alt_overrd
+ - alt_part_i
+ - bett_tax
+ - cert_part
+ - glass_flag
+ - lbr_hrs_j
+ - lbr_inc
+ - lbr_op_j
+ - lbr_tax
+ - lbr_typ_j
+ - misc_sublt
+ - misc_tax
+ - price_inc
+ - price_j
+ - tax_part
+ - est_seq
+ - paint_stg
+ - paint_tone
+ - part_qty
+ - unq_seq
+ - act_price
+ - bett_amt
+ - bett_pctg
+ - db_hrs
+ - db_price
+ - lbr_amt
+ - line_ref
+ - misc_amt
+ - mod_lb_hrs
+ - prt_dsmk_m
+ - prt_dsmk_p
+ - alt_co_id
+ - alt_partm
+ - alt_partno
+ - bett_type
+ - db_ref
+ - lbr_op
+ - line_desc
+ - line_ind
+ - mod_lbr_ty
+ - oem_partno
+ - op_code_desc
+ - part_type
+ - created_at
+ - updated_at
+ - id
+ - jobid
+ computed_fields: []
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1582224730652_update_permission_user_public_table_joblines/up.yaml b/hasura/migrations/1582224730652_update_permission_user_public_table_joblines/up.yaml
new file mode 100644
index 000000000..c639e07f8
--- /dev/null
+++ b/hasura/migrations/1582224730652_update_permission_user_public_table_joblines/up.yaml
@@ -0,0 +1,74 @@
+- args:
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: drop_select_permission
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - alt_overrd
+ - alt_part_i
+ - bett_tax
+ - cert_part
+ - glass_flag
+ - lbr_hrs_j
+ - lbr_inc
+ - lbr_op_j
+ - lbr_tax
+ - lbr_typ_j
+ - misc_sublt
+ - misc_tax
+ - price_inc
+ - price_j
+ - tax_part
+ - est_seq
+ - paint_stg
+ - paint_tone
+ - part_qty
+ - unq_seq
+ - act_price
+ - bett_amt
+ - bett_pctg
+ - db_hrs
+ - db_price
+ - lbr_amt
+ - line_ref
+ - misc_amt
+ - mod_lb_hrs
+ - prt_dsmk_m
+ - prt_dsmk_p
+ - alt_co_id
+ - alt_partm
+ - alt_partno
+ - bett_type
+ - db_ref
+ - lbr_op
+ - line_desc
+ - line_ind
+ - mod_lbr_ty
+ - oem_partno
+ - op_code_desc
+ - part_type
+ - status
+ - created_at
+ - updated_at
+ - id
+ - jobid
+ computed_fields: []
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1582224737046_update_permission_user_public_table_joblines/down.yaml b/hasura/migrations/1582224737046_update_permission_user_public_table_joblines/down.yaml
new file mode 100644
index 000000000..befcc2bb7
--- /dev/null
+++ b/hasura/migrations/1582224737046_update_permission_user_public_table_joblines/down.yaml
@@ -0,0 +1,75 @@
+- args:
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: drop_update_permission
+- args:
+ permission:
+ columns:
+ - alt_overrd
+ - alt_part_i
+ - bett_tax
+ - cert_part
+ - glass_flag
+ - lbr_hrs_j
+ - lbr_inc
+ - lbr_op_j
+ - lbr_tax
+ - lbr_typ_j
+ - misc_sublt
+ - misc_tax
+ - price_inc
+ - price_j
+ - tax_part
+ - est_seq
+ - paint_stg
+ - paint_tone
+ - part_qty
+ - unq_seq
+ - act_price
+ - bett_amt
+ - bett_pctg
+ - db_hrs
+ - db_price
+ - lbr_amt
+ - line_ref
+ - misc_amt
+ - mod_lb_hrs
+ - prt_dsmk_m
+ - prt_dsmk_p
+ - alt_co_id
+ - alt_partm
+ - alt_partno
+ - bett_type
+ - db_ref
+ - lbr_op
+ - line_desc
+ - line_ind
+ - mod_lbr_ty
+ - oem_partno
+ - op_code_desc
+ - part_type
+ - created_at
+ - updated_at
+ - id
+ - jobid
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: create_update_permission
diff --git a/hasura/migrations/1582224737046_update_permission_user_public_table_joblines/up.yaml b/hasura/migrations/1582224737046_update_permission_user_public_table_joblines/up.yaml
new file mode 100644
index 000000000..a04baffe3
--- /dev/null
+++ b/hasura/migrations/1582224737046_update_permission_user_public_table_joblines/up.yaml
@@ -0,0 +1,76 @@
+- args:
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: drop_update_permission
+- args:
+ permission:
+ columns:
+ - alt_overrd
+ - alt_part_i
+ - bett_tax
+ - cert_part
+ - glass_flag
+ - lbr_hrs_j
+ - lbr_inc
+ - lbr_op_j
+ - lbr_tax
+ - lbr_typ_j
+ - misc_sublt
+ - misc_tax
+ - price_inc
+ - price_j
+ - tax_part
+ - est_seq
+ - paint_stg
+ - paint_tone
+ - part_qty
+ - unq_seq
+ - act_price
+ - bett_amt
+ - bett_pctg
+ - db_hrs
+ - db_price
+ - lbr_amt
+ - line_ref
+ - misc_amt
+ - mod_lb_hrs
+ - prt_dsmk_m
+ - prt_dsmk_p
+ - alt_co_id
+ - alt_partm
+ - alt_partno
+ - bett_type
+ - db_ref
+ - lbr_op
+ - line_desc
+ - line_ind
+ - mod_lbr_ty
+ - oem_partno
+ - op_code_desc
+ - part_type
+ - status
+ - created_at
+ - updated_at
+ - id
+ - jobid
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: joblines
+ schema: public
+ type: create_update_permission
diff --git a/hasura/migrations/1582587971597_create_table_public_invoices/down.yaml b/hasura/migrations/1582587971597_create_table_public_invoices/down.yaml
new file mode 100644
index 000000000..78d023a7a
--- /dev/null
+++ b/hasura/migrations/1582587971597_create_table_public_invoices/down.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: DROP TABLE "public"."invoices";
+ type: run_sql
diff --git a/hasura/migrations/1582587971597_create_table_public_invoices/up.yaml b/hasura/migrations/1582587971597_create_table_public_invoices/up.yaml
new file mode 100644
index 000000000..55008cf33
--- /dev/null
+++ b/hasura/migrations/1582587971597_create_table_public_invoices/up.yaml
@@ -0,0 +1,28 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
+ type: run_sql
+- args:
+ cascade: false
+ read_only: false
+ sql: "CREATE TABLE \"public\".\"invoices\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
+ \"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
+ NOT NULL DEFAULT now(), \"vendorid\" uuid NOT NULL, \"jobid\" uuid NOT NULL,
+ \"date\" date NOT NULL DEFAULT now(), \"due_date\" date, \"exported\" boolean
+ NOT NULL DEFAULT false, \"exported_at\" timestamptz, \"is_credit_memo\" boolean
+ NOT NULL DEFAULT false, \"total\" numeric NOT NULL DEFAULT 0, \"invoice_number\"
+ text NOT NULL, PRIMARY KEY (\"id\") , FOREIGN KEY (\"jobid\") REFERENCES \"public\".\"jobs\"(\"id\")
+ ON UPDATE restrict ON DELETE cascade, FOREIGN KEY (\"vendorid\") REFERENCES
+ \"public\".\"vendors\"(\"id\") ON UPDATE restrict ON DELETE restrict, UNIQUE
+ (\"jobid\"));\nCREATE OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
+ TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
+ = NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_invoices_updated_at\"\nBEFORE
+ UPDATE ON \"public\".\"invoices\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
+ ON TRIGGER \"set_public_invoices_updated_at\" ON \"public\".\"invoices\" \nIS
+ 'trigger to set value of column \"updated_at\" to current timestamp on row update';"
+ type: run_sql
+- args:
+ name: invoices
+ schema: public
+ type: add_existing_table_or_view
diff --git a/hasura/migrations/1582587989233_track_all_relationships/down.yaml b/hasura/migrations/1582587989233_track_all_relationships/down.yaml
new file mode 100644
index 000000000..3c27d1b54
--- /dev/null
+++ b/hasura/migrations/1582587989233_track_all_relationships/down.yaml
@@ -0,0 +1,24 @@
+- args:
+ relationship: job
+ table:
+ name: invoices
+ schema: public
+ type: drop_relationship
+- args:
+ relationship: vendor
+ table:
+ name: invoices
+ schema: public
+ type: drop_relationship
+- args:
+ relationship: invoice
+ table:
+ name: jobs
+ schema: public
+ type: drop_relationship
+- args:
+ relationship: invoices
+ table:
+ name: vendors
+ schema: public
+ type: drop_relationship
diff --git a/hasura/migrations/1582587989233_track_all_relationships/up.yaml b/hasura/migrations/1582587989233_track_all_relationships/up.yaml
new file mode 100644
index 000000000..266395edb
--- /dev/null
+++ b/hasura/migrations/1582587989233_track_all_relationships/up.yaml
@@ -0,0 +1,41 @@
+- args:
+ name: job
+ table:
+ name: invoices
+ schema: public
+ using:
+ foreign_key_constraint_on: jobid
+ type: create_object_relationship
+- args:
+ name: vendor
+ table:
+ name: invoices
+ schema: public
+ using:
+ foreign_key_constraint_on: vendorid
+ type: create_object_relationship
+- args:
+ name: invoice
+ table:
+ name: jobs
+ schema: public
+ using:
+ manual_configuration:
+ column_mapping:
+ id: jobid
+ remote_table:
+ name: invoices
+ schema: public
+ type: create_object_relationship
+- args:
+ name: invoices
+ table:
+ name: vendors
+ schema: public
+ using:
+ foreign_key_constraint_on:
+ column: vendorid
+ table:
+ name: invoices
+ schema: public
+ type: create_array_relationship
diff --git a/hasura/migrations/1582588052326_update_permission_user_public_table_invoices/down.yaml b/hasura/migrations/1582588052326_update_permission_user_public_table_invoices/down.yaml
new file mode 100644
index 000000000..dadeb8ce0
--- /dev/null
+++ b/hasura/migrations/1582588052326_update_permission_user_public_table_invoices/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoices
+ schema: public
+ type: drop_insert_permission
diff --git a/hasura/migrations/1582588052326_update_permission_user_public_table_invoices/up.yaml b/hasura/migrations/1582588052326_update_permission_user_public_table_invoices/up.yaml
new file mode 100644
index 000000000..33a70c629
--- /dev/null
+++ b/hasura/migrations/1582588052326_update_permission_user_public_table_invoices/up.yaml
@@ -0,0 +1,35 @@
+- args:
+ permission:
+ allow_upsert: true
+ check:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ columns:
+ - id
+ - created_at
+ - updated_at
+ - vendorid
+ - jobid
+ - date
+ - due_date
+ - exported
+ - exported_at
+ - is_credit_memo
+ - total
+ - invoice_number
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: invoices
+ schema: public
+ type: create_insert_permission
diff --git a/hasura/migrations/1582588061657_update_permission_user_public_table_invoices/down.yaml b/hasura/migrations/1582588061657_update_permission_user_public_table_invoices/down.yaml
new file mode 100644
index 000000000..9ce2f6b37
--- /dev/null
+++ b/hasura/migrations/1582588061657_update_permission_user_public_table_invoices/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoices
+ schema: public
+ type: drop_select_permission
diff --git a/hasura/migrations/1582588061657_update_permission_user_public_table_invoices/up.yaml b/hasura/migrations/1582588061657_update_permission_user_public_table_invoices/up.yaml
new file mode 100644
index 000000000..393b945c5
--- /dev/null
+++ b/hasura/migrations/1582588061657_update_permission_user_public_table_invoices/up.yaml
@@ -0,0 +1,33 @@
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - exported
+ - is_credit_memo
+ - date
+ - due_date
+ - total
+ - invoice_number
+ - created_at
+ - exported_at
+ - updated_at
+ - id
+ - jobid
+ - vendorid
+ computed_fields: []
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ limit: null
+ role: user
+ table:
+ name: invoices
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1582588071369_update_permission_user_public_table_invoices/down.yaml b/hasura/migrations/1582588071369_update_permission_user_public_table_invoices/down.yaml
new file mode 100644
index 000000000..f221ea65f
--- /dev/null
+++ b/hasura/migrations/1582588071369_update_permission_user_public_table_invoices/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoices
+ schema: public
+ type: drop_update_permission
diff --git a/hasura/migrations/1582588071369_update_permission_user_public_table_invoices/up.yaml b/hasura/migrations/1582588071369_update_permission_user_public_table_invoices/up.yaml
new file mode 100644
index 000000000..add508bd2
--- /dev/null
+++ b/hasura/migrations/1582588071369_update_permission_user_public_table_invoices/up.yaml
@@ -0,0 +1,34 @@
+- args:
+ permission:
+ columns:
+ - exported
+ - is_credit_memo
+ - date
+ - due_date
+ - total
+ - invoice_number
+ - created_at
+ - exported_at
+ - updated_at
+ - id
+ - jobid
+ - vendorid
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: invoices
+ schema: public
+ type: create_update_permission
diff --git a/hasura/migrations/1582588465637_create_table_public_invoicelines/down.yaml b/hasura/migrations/1582588465637_create_table_public_invoicelines/down.yaml
new file mode 100644
index 000000000..3c7b9280f
--- /dev/null
+++ b/hasura/migrations/1582588465637_create_table_public_invoicelines/down.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: DROP TABLE "public"."invoicelines";
+ type: run_sql
diff --git a/hasura/migrations/1582588465637_create_table_public_invoicelines/up.yaml b/hasura/migrations/1582588465637_create_table_public_invoicelines/up.yaml
new file mode 100644
index 000000000..cc2bed198
--- /dev/null
+++ b/hasura/migrations/1582588465637_create_table_public_invoicelines/up.yaml
@@ -0,0 +1,26 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
+ type: run_sql
+- args:
+ cascade: false
+ read_only: false
+ sql: "CREATE TABLE \"public\".\"invoicelines\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
+ \"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
+ NOT NULL DEFAULT now(), \"invoiceid\" uuid NOT NULL, \"line_desc\" text, \"actual_price\"
+ numeric NOT NULL DEFAULT 0, \"actual_cost\" numeric NOT NULL DEFAULT 0, \"cost_center\"
+ text NOT NULL, \"estlindid\" uuid, PRIMARY KEY (\"id\") , FOREIGN KEY (\"invoiceid\")
+ REFERENCES \"public\".\"invoices\"(\"id\") ON UPDATE restrict ON DELETE cascade);\nCREATE
+ OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
+ TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
+ = NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_invoicelines_updated_at\"\nBEFORE
+ UPDATE ON \"public\".\"invoicelines\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
+ ON TRIGGER \"set_public_invoicelines_updated_at\" ON \"public\".\"invoicelines\"
+ \nIS 'trigger to set value of column \"updated_at\" to current timestamp on
+ row update';"
+ type: run_sql
+- args:
+ name: invoicelines
+ schema: public
+ type: add_existing_table_or_view
diff --git a/hasura/migrations/1582588478430_track_all_relationships/down.yaml b/hasura/migrations/1582588478430_track_all_relationships/down.yaml
new file mode 100644
index 000000000..b87036054
--- /dev/null
+++ b/hasura/migrations/1582588478430_track_all_relationships/down.yaml
@@ -0,0 +1,12 @@
+- args:
+ relationship: invoice
+ table:
+ name: invoicelines
+ schema: public
+ type: drop_relationship
+- args:
+ relationship: invoicelines
+ table:
+ name: invoices
+ schema: public
+ type: drop_relationship
diff --git a/hasura/migrations/1582588478430_track_all_relationships/up.yaml b/hasura/migrations/1582588478430_track_all_relationships/up.yaml
new file mode 100644
index 000000000..c2420cefb
--- /dev/null
+++ b/hasura/migrations/1582588478430_track_all_relationships/up.yaml
@@ -0,0 +1,20 @@
+- args:
+ name: invoice
+ table:
+ name: invoicelines
+ schema: public
+ using:
+ foreign_key_constraint_on: invoiceid
+ type: create_object_relationship
+- args:
+ name: invoicelines
+ table:
+ name: invoices
+ schema: public
+ using:
+ foreign_key_constraint_on:
+ column: invoiceid
+ table:
+ name: invoicelines
+ schema: public
+ type: create_array_relationship
diff --git a/hasura/migrations/1582588520891_update_permission_user_public_table_invoicelines/down.yaml b/hasura/migrations/1582588520891_update_permission_user_public_table_invoicelines/down.yaml
new file mode 100644
index 000000000..8c11bf4b6
--- /dev/null
+++ b/hasura/migrations/1582588520891_update_permission_user_public_table_invoicelines/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: drop_insert_permission
diff --git a/hasura/migrations/1582588520891_update_permission_user_public_table_invoicelines/up.yaml b/hasura/migrations/1582588520891_update_permission_user_public_table_invoicelines/up.yaml
new file mode 100644
index 000000000..e174b2077
--- /dev/null
+++ b/hasura/migrations/1582588520891_update_permission_user_public_table_invoicelines/up.yaml
@@ -0,0 +1,33 @@
+- args:
+ permission:
+ allow_upsert: true
+ check:
+ invoice:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ columns:
+ - id
+ - created_at
+ - updated_at
+ - invoiceid
+ - line_desc
+ - actual_price
+ - actual_cost
+ - cost_center
+ - estlindid
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: create_insert_permission
diff --git a/hasura/migrations/1582588525612_update_permission_user_public_table_invoicelines/down.yaml b/hasura/migrations/1582588525612_update_permission_user_public_table_invoicelines/down.yaml
new file mode 100644
index 000000000..cf0af8966
--- /dev/null
+++ b/hasura/migrations/1582588525612_update_permission_user_public_table_invoicelines/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: drop_select_permission
diff --git a/hasura/migrations/1582588525612_update_permission_user_public_table_invoicelines/up.yaml b/hasura/migrations/1582588525612_update_permission_user_public_table_invoicelines/up.yaml
new file mode 100644
index 000000000..14f825a78
--- /dev/null
+++ b/hasura/migrations/1582588525612_update_permission_user_public_table_invoicelines/up.yaml
@@ -0,0 +1,31 @@
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - actual_cost
+ - actual_price
+ - cost_center
+ - line_desc
+ - created_at
+ - updated_at
+ - estlindid
+ - id
+ - invoiceid
+ computed_fields: []
+ filter:
+ invoice:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ limit: null
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1582588530315_update_permission_user_public_table_invoicelines/down.yaml b/hasura/migrations/1582588530315_update_permission_user_public_table_invoicelines/down.yaml
new file mode 100644
index 000000000..5df677663
--- /dev/null
+++ b/hasura/migrations/1582588530315_update_permission_user_public_table_invoicelines/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: drop_update_permission
diff --git a/hasura/migrations/1582588530315_update_permission_user_public_table_invoicelines/up.yaml b/hasura/migrations/1582588530315_update_permission_user_public_table_invoicelines/up.yaml
new file mode 100644
index 000000000..756147e8f
--- /dev/null
+++ b/hasura/migrations/1582588530315_update_permission_user_public_table_invoicelines/up.yaml
@@ -0,0 +1,32 @@
+- args:
+ permission:
+ columns:
+ - actual_cost
+ - actual_price
+ - cost_center
+ - line_desc
+ - created_at
+ - updated_at
+ - estlindid
+ - id
+ - invoiceid
+ filter:
+ invoice:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ localPresets:
+ - key: ""
+ value: ""
+ set: {}
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: create_update_permission
diff --git a/hasura/migrations/1582588553560_update_permission_user_public_table_invoicelines/down.yaml b/hasura/migrations/1582588553560_update_permission_user_public_table_invoicelines/down.yaml
new file mode 100644
index 000000000..f5bb498ae
--- /dev/null
+++ b/hasura/migrations/1582588553560_update_permission_user_public_table_invoicelines/down.yaml
@@ -0,0 +1,6 @@
+- args:
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: drop_delete_permission
diff --git a/hasura/migrations/1582588553560_update_permission_user_public_table_invoicelines/up.yaml b/hasura/migrations/1582588553560_update_permission_user_public_table_invoicelines/up.yaml
new file mode 100644
index 000000000..174807864
--- /dev/null
+++ b/hasura/migrations/1582588553560_update_permission_user_public_table_invoicelines/up.yaml
@@ -0,0 +1,18 @@
+- args:
+ permission:
+ filter:
+ invoice:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ role: user
+ table:
+ name: invoicelines
+ schema: public
+ type: create_delete_permission
diff --git a/package.json b/package.json
index ebb9e31f7..0c7a238fa 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,6 @@
"concurrently": "^4.0.1",
"eslint": "^6.7.2",
"eslint-plugin-promise": "^4.2.1",
- "hasura-cli": "^1.0.0-beta.10"
+ "hasura-cli": "^1.1.0"
}
}
diff --git a/yarn.lock b/yarn.lock
index c72d7c808..6f36c5799 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1950,10 +1950,10 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
-hasura-cli@^1.0.0-beta.10:
- version "1.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.0.0-rc.1.tgz#481453f88e7624f468f329c75a88fbbde3407f00"
- integrity sha512-w6DGAhJZ6l7U89SD6QIxYetP3/dDxJc4jEVzjMAYsueeYWQKDeGgtZBFBW1sdgAlUtRmtIa5wbiFLXuagB6JqA==
+hasura-cli@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.1.0.tgz#a095e94c654d30354d8979602b8c0047c7b8a9e1"
+ integrity sha512-D1qXoYydx9Mgq7VQdCmOOvTlYhd1RcjQtn4s7pN1wb5w1ORIcDFLm1rS3w97bsx7wPRotIl0reyhc3+FDq+FFg==
dependencies:
axios "^0.19.0"
chalk "^2.4.2"