diff --git a/client/src/components/chat-messages-list/chat-message-list.styles.scss b/client/src/components/chat-messages-list/chat-message-list.styles.scss index 1c543cfaa..e6e1bd8ce 100644 --- a/client/src/components/chat-messages-list/chat-message-list.styles.scss +++ b/client/src/components/chat-messages-list/chat-message-list.styles.scss @@ -56,10 +56,11 @@ } .messages ul li p { display: inline-block; - padding: 10px 15px; + margin: 0px; + padding: 0px 10px; border-radius: 20px; max-width: 205px; - line-height: 130%; + //line-height: 130%; } @media screen and (min-width: 735px) { .messages ul li p { diff --git a/client/src/components/invoice-detail-edit/invoice-detail-edit.component.jsx b/client/src/components/invoice-detail-edit/invoice-detail-edit.component.jsx new file mode 100644 index 000000000..73acfd027 --- /dev/null +++ b/client/src/components/invoice-detail-edit/invoice-detail-edit.component.jsx @@ -0,0 +1,106 @@ +import { DatePicker, Form, Input, Switch } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; +import JobSearchSelect from "../job-search-select/job-search-select.component"; +import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; +import InvoiceEnterModalLinesComponent from "../invoice-enter-modal/invoice-enter-modal.lines.component"; +import DocumentsUploadContainer from "../documents-upload/documents-upload.container"; + +export default function InvoiceDetailEditComponent({ + form, + roAutoCompleteOptions, + loadLines, + lineData, + responsibilityCenters, +}) { + const { t } = useTranslation(); + return ( +
+
+ + { + if (form.getFieldValue("jobid") !== null) { + //loadLines({ variables: { id: form.getFieldValue("jobid") } }); + } + }} + /> + +
+
+ + + + + + + + + + + + +
+ + + + + + + +
+ ); +} diff --git a/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx b/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx new file mode 100644 index 000000000..e51e3a399 --- /dev/null +++ b/client/src/components/invoice-detail-edit/invoice-detail-edit.container.jsx @@ -0,0 +1,93 @@ +import React, { useState, useEffect } from "react"; +import { + QUERY_INVOICES_BY_VENDOR, + QUERY_INVOICE_BY_PK, +} from "../../graphql/invoices.queries"; +import { useQuery, useLazyQuery } from "@apollo/react-hooks"; +import queryString from "query-string"; +import { useHistory, useLocation } from "react-router-dom"; +import { Table, Input, Form } from "antd"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; +import AlertComponent from "../alert/alert.component"; +import { DateFormatter } from "../../utils/DateFormatter"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import InvoiceDetailEditComponent from "./invoice-detail-edit.component"; +import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; +import moment from "moment"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; +import { connect } from "react-redux"; +import { GET_JOB_LINES_TO_ENTER_INVOICE } from "../../graphql/jobs-lines.queries"; +import { ACTIVE_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +export function InvoiceDetailEditContainer({ bodyshop }) { + const search = queryString.parse(useLocation().search); + const { t } = useTranslation(); + const [form] = Form.useForm(); + + const { loading, error, data } = useQuery(QUERY_INVOICE_BY_PK, { + variables: { invoiceid: search.invoiceid }, + skip: !!!search.invoiceid, + }); + + const { data: RoAutoCompleteData } = useQuery(ACTIVE_JOBS_FOR_AUTOCOMPLETE, { + fetchPolicy: "network-only", + variables: { statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"] }, + }); + + const { + loading: linesLoading, + data: lineData, + refetch: loadLines, + } = useQuery(GET_JOB_LINES_TO_ENTER_INVOICE, { + variables: { id: data && data.invoices_by_pk.jobid }, + fetchPolicy: "network-only", + skip: !!!(data && data.invoices_by_pk.id), + }); + + const handleFinish = (values) => { + console.log("values", values); + }; + + useEffect(() => { + // if (data) { + // loadLines(); + // if (lineData) //form.resetFields(); + // } + }, [data, lineData]); + + if (error) return ; + return ( + +
+ + +
+ ); +} +export default connect(mapStateToProps, null)(InvoiceDetailEditContainer); diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx b/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx index 5558979ee..2ce0bff69 100644 --- a/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx +++ b/client/src/components/invoice-enter-modal/invoice-enter-modal.lines.component.jsx @@ -9,7 +9,7 @@ export default function InvoiceEnterModalLinesComponent({ lineData, discount, form, - responsibilityCenters + responsibilityCenters, }) { const { t } = useTranslation(); const { setFieldsValue, getFieldsValue } = form; @@ -25,14 +25,15 @@ export default function InvoiceEnterModalLinesComponent({ acc + (value && value.actual_cost ? value.actual_cost : 0), 0 ) - : 0 + : 0, }); }; return (
- + {(fields, { add, remove }) => { + console.log("fields", fields); return (
{fields.map((field, index) => ( @@ -44,8 +45,8 @@ export default function InvoiceEnterModalLinesComponent({ rules={[ { required: true, - message: t("general.validation.required") - } + message: t("general.validation.required"), + }, ]} > - {getFieldsValue("invoicelines").invoicelines[index] && - getFieldsValue("invoicelines").invoicelines[index] - .joblinename && - !getFieldsValue("invoicelines").invoicelines[index] - .joblineid ? ( - - - - ) : null} + + { + //TODO Will need to refactor this to use proper form components in the search select above. + getFieldsValue("invoicelines")[index] && + // getFieldsValue("invoicelines")[index].joblinename && + !getFieldsValue("invoicelines").invoicelines[index] + .joblineid ? ( + + + + ) : null + } { + onBlur={(e) => { setFieldsValue({ invoicelines: getFieldsValue( "invoicelines" @@ -157,11 +161,11 @@ export default function InvoiceEnterModalLinesComponent({ actual_cost: !!item.actual_cost ? item.actual_cost : parseFloat(e.target.value) * - (1 - discount) + (1 - discount), }; } return item; - }) + }), }); }} /> @@ -173,8 +177,8 @@ export default function InvoiceEnterModalLinesComponent({ rules={[ { required: true, - message: t("general.validation.required") - } + message: t("general.validation.required"), + }, ]} > calculateTotals()} /> @@ -186,12 +190,12 @@ export default function InvoiceEnterModalLinesComponent({ rules={[ { required: true, - message: t("general.validation.required") - } + message: t("general.validation.required"), + }, ]} > diff --git a/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx b/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx new file mode 100644 index 000000000..9d68097fd --- /dev/null +++ b/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx @@ -0,0 +1,132 @@ +import React, { useState } from "react"; +import { QUERY_INVOICES_BY_VENDOR } from "../../graphql/invoices.queries"; +import { useQuery } from "@apollo/react-hooks"; +import queryString from "query-string"; +import { useHistory, useLocation } from "react-router-dom"; +import { Table, Input } from "antd"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; +import AlertComponent from "../alert/alert.component"; +import { DateFormatter } from "../../utils/DateFormatter"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; + +export default function InvoicesByVendorList() { + const search = queryString.parse(useLocation().search); + const history = useHistory(); + + const { loading, error, data } = useQuery(QUERY_INVOICES_BY_VENDOR, { + variables: { vendorId: search.vendorid }, + skip: !!!search.vendorid, + }); + + const { t } = useTranslation(); + + const [state, setState] = useState({ + sortedInfo: {}, + search: "", + }); + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + search.invoiceid = record.id; + history.push({ search: queryString.stringify(search) }); + } + } else { + delete search.invoiceid; + history.push({ search: queryString.stringify(search) }); + } + }; + + const columns = [ + { + title: t("invoices.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: + state.sortedInfo.columnKey === "invoice_number" && + state.sortedInfo.order, + }, + { + title: t("invoices.fields.date"), + dataIndex: "date", + key: "date", + + sorter: (a, b) => a.date - b.date, + sortOrder: + state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date}, + }, + { + title: t("invoices.fields.total"), + dataIndex: "total", + key: "total", + + sorter: (a, b) => a.total - b.total, + sortOrder: + state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => ( + {record.total} + ), + }, + ]; + + const handleSearch = (e) => { + setState({ ...state, search: e.target.value }); + }; + + const dataSource = state.search + ? data.invoices.filter( + (i) => + (i.invoice_number || "") + .toLowerCase() + .includes(state.search.toLowerCase()) || + (i.amount || "").toString().includes(state.search) + ) + : (data && data.invoices) || []; + + if (error) return ; + + return ( + { + return ( +
+ +
+ ); + }} + dataSource={dataSource} + size="small" + pagination={{ position: "top" }} + columns={columns} + rowKey="id" + onChange={handleTableChange} + rowSelection={{ + onSelect: (record) => { + handleOnRowClick(record); + }, + selectedRowKeys: [search.invoiceid], + type: "radio", + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + }, // click row + }; + }} + /> + ); +} diff --git a/client/src/components/invoices-vendors-list/invoices-vendors-list.component.jsx b/client/src/components/invoices-vendors-list/invoices-vendors-list.component.jsx new file mode 100644 index 000000000..930c362fa --- /dev/null +++ b/client/src/components/invoices-vendors-list/invoices-vendors-list.component.jsx @@ -0,0 +1,119 @@ +import React, { useState } from "react"; +import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries"; +import { useQuery } from "@apollo/react-hooks"; +import queryString from "query-string"; +import { useHistory, useLocation } from "react-router-dom"; +import { Table, Input } from "antd"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; +import AlertComponent from "../alert/alert.component"; + +export default function InvoicesVendorsList() { + const search = queryString.parse(useLocation().search); + const history = useHistory(); + + const { loading, error, data } = useQuery(QUERY_ALL_VENDORS); + + const { t } = useTranslation(); + + const [state, setState] = useState({ + sortedInfo: {}, + search: "", + }); + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const columns = [ + { + title: t("vendors.fields.name"), + dataIndex: "name", + key: "name", + sorter: (a, b) => alphaSort(a.name, b.name), + sortOrder: + state.sortedInfo.columnKey === "name" && state.sortedInfo.order, + }, + { + title: t("vendors.fields.cost_center"), + dataIndex: "cost_center", + key: "cost_center", + sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), + sortOrder: + state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, + }, + { + title: t("vendors.fields.city"), + dataIndex: "city", + key: "city", + }, + ]; + + const handleOnRowClick = (record) => { + if (record) { + delete search.invoiceid; + if (record.id) { + search.vendorid = record.id; + history.push({ search: queryString.stringify(search) }); + } + } else { + delete search.vendorid; + history.push({ search: queryString.stringify(search) }); + } + }; + + const handleSearch = (e) => { + setState({ ...state, search: e.target.value }); + }; + + if (error) return ; + + const dataSource = state.search + ? data.vendors.filter( + (v) => + (v.name || "").toLowerCase().includes(state.search.toLowerCase()) || + (v.cost_center || "") + .toLowerCase() + .includes(state.search.toLowerCase()) || + (v.city || "").toLowerCase().includes(state.search.toLowerCase()) + ) + : (data && data.vendors) || []; + + return ( +
{ + return ( +
+ +
+ ); + }} + dataSource={dataSource} + size="small" + pagination={{ position: "top" }} + columns={columns} + rowKey="id" + onChange={handleTableChange} + rowSelection={{ + onSelect: (record) => { + handleOnRowClick(record); + }, + selectedRowKeys: [search.vendorid], + type: "radio", + }} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + }, // click row + }; + }} + /> + ); +} diff --git a/client/src/components/vendor-search-select/vendor-search-select.component.jsx b/client/src/components/vendor-search-select/vendor-search-select.component.jsx index ce2bda4ef..d87b6f7bb 100644 --- a/client/src/components/vendor-search-select/vendor-search-select.component.jsx +++ b/client/src/components/vendor-search-select/vendor-search-select.component.jsx @@ -18,15 +18,15 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect }) => { showSearch value={option} style={{ - width: 300 + width: 300, }} onChange={setOption} - optionFilterProp="children" + optionFilterProp="name" onSelect={onSelect} > {options - ? options.map(o => ( -
({ ...item }))} - rowKey="id" - dataSource={record.invoicelines} - /> - - ); - }; - - return ( -
{ - handleOnRowClick(page * pageSize); - }, - }} - columns={columns.map((item) => ({ ...item }))} - rowKey="id" - dataSource={invoices} - onChange={handleTableChange} - expandable={{ - expandedRowKeys: [selectedInvoice], - onExpand: (expanded, record) => { - handleOnRowClick(expanded ? record : null); - }, - }} - rowSelection={{ - onSelect: (record) => { - handleOnRowClick(record); - }, - selectedRowKeys: [selectedInvoice], - type: "radio", - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, // click row - onDoubleClick: (event) => {}, // double click row - onContextMenu: (event) => {}, // right button click row - onMouseEnter: (event) => {}, // mouse enter row - onMouseLeave: (event) => {}, // mouse leave row - }; - }} - /> - ); -} diff --git a/client/src/pages/invoices/invoices.page.container.jsx b/client/src/pages/invoices/invoices.page.container.jsx index 78a2e4ee3..ca881c561 100644 --- a/client/src/pages/invoices/invoices.page.container.jsx +++ b/client/src/pages/invoices/invoices.page.container.jsx @@ -1,57 +1,23 @@ -import { useQuery } from "@apollo/react-hooks"; +import { Col, Row } from "antd"; import React from "react"; -import { QUERY_ALL_INVOICES_PAGINATED } from "../../graphql/invoices.queries"; -import InvoicesPageComponent from "./invoices.page.component"; -import AlertComponent from "../../components/alert/alert.component"; -import queryString from "query-string"; -import { useHistory, useLocation } from "react-router-dom"; +import InvoicesByVendorList from "../../components/invoices-by-vendor-list/invoices-by-vendor-list.component"; +import VendorsList from "../../components/invoices-vendors-list/invoices-vendors-list.component"; +import InvoiceDetailEditContainer from "../../components/invoice-detail-edit/invoice-detail-edit.container"; export default function InvoicesPageContainer() { - const { loading, error, data, fetchMore } = useQuery( - QUERY_ALL_INVOICES_PAGINATED, - { - variables: { offset: 0, limit: 1 }, - } - ); - const search = queryString.parse(useLocation().search); - const history = useHistory(); - - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - search.invoiceid = record.id; - history.push({ search: queryString.stringify(search) }); - } - } else { - delete search.invoiceid; - history.push({ search: queryString.stringify(search) }); - } - }; - - const handleFetchMore = (offset) => { - fetchMore({ - variables: { - offset: offset, - }, - updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult) return prev; - return Object.assign({}, prev, { - invoices: [...prev.invoices, ...fetchMoreResult.invoices], - }); - }, - }); - }; - - if (error) return ; return ( -
- -
+ +
+ + + + + + + + + + + ); } diff --git a/server/sms/receive.js b/server/sms/receive.js index 9aad4fca8..b26300ca4 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -60,8 +60,6 @@ exports.receive = (req, res) => { Object.keys(i).map((k) => allTokens.push(k)) ); const uniqueTokens = [...new Set(allTokens)]; - console.log("exports.receive -> uniqueTokens", uniqueTokens); - var message = { notification: { title: `New SMS From ${phone(req.body.From)[0]}`, @@ -80,7 +78,10 @@ exports.receive = (req, res) => { .sendMulticast(message) .then((response) => { // Response is a message ID string. - console.log("Successfully sent message:", response); + console.log( + "Successfully sent message:", + JSON.stringify(response) + ); }) .catch((error) => { console.log("Error sending message:", error);