diff --git a/client/package.json b/client/package.json index faf0a06e0..8abcd7f09 100644 --- a/client/package.json +++ b/client/package.json @@ -25,6 +25,7 @@ "react-barcode": "^1.4.0", "react-big-calendar": "^0.24.0", "react-dom": "^16.13.0", + "react-grid-gallery": "^0.5.5", "react-grid-layout": "^0.18.2", "react-html-email": "^3.0.0", "react-i18next": "^11.3.3", diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index c58b41c32..441f38302 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -1,54 +1,64 @@ import { Card } from "antd"; import React, { useState } from "react"; import { Responsive, WidthProvider } from "react-grid-layout"; +import styled from "styled-components"; //Combination of the following: // /node_modules/react-grid-layout/css/styles.css // /node_modules/react-resizable/css/styles.css import "./dashboard-grid.styles.css"; +const Sdiv = styled.div` + position: absolute; + height: 80%; + width: 80%; + top: 10%; + left: 10%; + background-color: #ffcc00; +`; + const ResponsiveReactGridLayout = WidthProvider(Responsive); export default function DashboardGridComponent() { const [state, setState] = useState({ layout: [ - { x: 0, y: 0, w: 2, h: 2 }, - { x: 1, y: 0, w: 2, h: 2 }, - { x: 4, y: 0, w: 2, h: 2 } + { i: "1", x: 0, y: 0, w: 2, h: 2 }, + { i: "2", x: 2, y: 0, w: 2, h: 2 }, + { i: "3", x: 4, y: 0, w: 2, h: 2 } ] }); const defaultProps = { className: "layout", - breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, - cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }, - rowHeight: 100 + breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 } + // cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }, + // rowHeight: 100 }; // We're using the cols coming back from this to calculate where to add new items. const onBreakpointChange = (breakpoint, cols) => { console.log("breakpoint, cols", breakpoint, cols); - setState({ ...state, breakpoint: breakpoint, cols: cols }); + // setState({ ...state, breakpoint: breakpoint, cols: cols }); }; return ( -
+ The Grid. { console.log("layout", layout); setState({ ...state, layout }); }} > {state.layout.map((item, index) => { - console.log("item", item); return ( - + A Card {index} ); })} -
+ ); } diff --git a/client/src/components/invoices-list-table/invoices-list-table.component.jsx b/client/src/components/invoices-list-table/invoices-list-table.component.jsx new file mode 100644 index 000000000..a1a4a2fa9 --- /dev/null +++ b/client/src/components/invoices-list-table/invoices-list-table.component.jsx @@ -0,0 +1,71 @@ +import { Table } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; +import { DateFormatter } from "../../utils/DateFormatter"; +export default function InvoicesListTableComponent({ loading, invoices }) { + const [state, setState] = useState({ + sortedInfo: {} + }); + const { t } = useTranslation(); + + const columns = [ + { + title: t("invoices.fields.vendorname"), + dataIndex: "vendorname", + key: "vendorname", + // onFilter: (value, record) => record.ro_number.includes(value), + // filteredValue: state.filteredInfo.text || null, + sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), + sortOrder: + state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, + //ellipsis: true, + render: (text, record) => {record.vendor.name} + }, + { + title: t("invoices.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + // onFilter: (value, record) => record.ro_number.includes(value), + // filteredValue: state.filteredInfo.text || null, + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: + state.sortedInfo.columnKey === "invoice_number" && + state.sortedInfo.order + //ellipsis: true, + }, + { + title: t("invoices.fields.date"), + dataIndex: "date", + key: "date", + // onFilter: (value, record) => record.ro_number.includes(value), + // filteredValue: state.filteredInfo.text || null, + sorter: (a, b) => a.date - b.date, + sortOrder: + state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + //ellipsis: true, + render: (text, record) => {record.date} + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const rowExpander = record => ( +
Invoice details
+ ); + + return ( + ({ ...item }))} + rowKey="id" + dataSource={invoices} + onChange={handleTableChange} + /> + ); +} diff --git a/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx b/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx index 7f29af510..df0c2108e 100644 --- a/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx +++ b/client/src/components/jobs-detail-pli/jobs-detail-pli.component.jsx @@ -1,17 +1,15 @@ +import { Button } from "antd"; import React from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { - toggleModalVisible, - setModalContext -} from "../../redux/modals/modals.actions"; -import { Button } from "antd"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component"; +import AlertComponent from "../alert/alert.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser }); const mapDispatchToProps = dispatch => ({ - toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")), setInvoiceEnterContext: context => dispatch(setModalContext({ context: context, modal: "invoiceEnter" })) }); @@ -19,9 +17,9 @@ export default connect( mapStateToProps, mapDispatchToProps )(function JobsDetailPliComponent({ - toggleModalVisible, setInvoiceEnterContext, - job + job, + invoicesQuery }) { return (
@@ -37,6 +35,13 @@ export default connect( > Enter Invoice + {invoicesQuery.error ? ( + + ) : null} +
); }); 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 index e6468ddcb..3330ce15a 100644 --- a/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx +++ b/client/src/components/jobs-detail-pli/jobs-detail-pli.container.jsx @@ -1,7 +1,12 @@ import React from "react"; +import { useQuery } from "react-apollo"; import JobsDetailPliComponent from "./jobs-detail-pli.component"; - +import { QUERY_INVOICES_BY_JOBID } from "../../graphql/invoices.queries"; export default function JobsDetailPliContainer({ job }) { - console.log("job", job); - return ; + const invoicesQuery = useQuery(QUERY_INVOICES_BY_JOBID, { + variables: { jobid: job.id }, + fetchPolicy: "network-only" + }); + + return ; } diff --git a/client/src/components/jobs-documents/jobs-documents.component.jsx b/client/src/components/jobs-documents/jobs-documents.component.jsx index 2ea672ba1..d28f97d9f 100644 --- a/client/src/components/jobs-documents/jobs-documents.component.jsx +++ b/client/src/components/jobs-documents/jobs-documents.component.jsx @@ -1,16 +1,17 @@ -import { Modal, notification, Upload } from "antd"; import { InboxOutlined } from "@ant-design/icons"; +import { Modal, notification, Upload } from "antd"; import axios from "axios"; import React, { useState } from "react"; import { useMutation } from "react-apollo"; +import Gallery from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import Resizer from "react-image-file-resizer"; import { - INSERT_NEW_DOCUMENT, - DELETE_DOCUMENT + DELETE_DOCUMENT, + INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; -import "./jobs-documents.styles.scss"; import { generateCdnThumb } from "../../utils/DocHelpers"; +import "./jobs-documents.styles.scss"; function getBase64(file) { return new Promise((resolve, reject) => { @@ -45,6 +46,19 @@ function JobsDocumentsComponent({ shopId, jobId, loading, data, currentUser }) { }, []) ); + const [galleryImages, setgalleryImages] = useState( + data.reduce((acc, value) => { + acc.push({ + src: value.url, + thumbnail: value.thumb_url, + thumbnailHeight: 150, + thumbnailWidth: 150, + isSelected: false + }); + return acc; + }, []) + ); + const uploadToS3 = ( fileName, fileType, @@ -246,6 +260,29 @@ function JobsDocumentsComponent({ shopId, jobId, loading, data, currentUser }) { example + + + { + console.log("index, image", index, image); + setgalleryImages( + galleryImages.map((g, idx) => + index === idx ? { ...g, isSelected: !g.isSelected } : g + ) + ); + }} + > ); } diff --git a/client/src/graphql/invoices.queries.js b/client/src/graphql/invoices.queries.js index 309813be6..9a52f38cf 100644 --- a/client/src/graphql/invoices.queries.js +++ b/client/src/graphql/invoices.queries.js @@ -9,3 +9,25 @@ export const INSERT_NEW_INVOICE = gql` } } `; + +export const QUERY_INVOICES_BY_JOBID = gql` + query QUERY_INVOICES_BY_JOBID($jobid: uuid!) { + invoices(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) { + id + vendor { + id + name + } + total + invoice_number + date + invoicelines { + actual_price + actual_cost + cost_center + id + line_desc + } + } + } +`; diff --git a/client/src/utils/DocHelpers.js b/client/src/utils/DocHelpers.js index be8d28632..8f3c6ea72 100644 --- a/client/src/utils/DocHelpers.js +++ b/client/src/utils/DocHelpers.js @@ -4,8 +4,8 @@ export const generateCdnThumb = key => { key: key, edits: { resize: { - height: 100, - width: 100 + height: 150, + width: 150 } } }); diff --git a/client/yarn.lock b/client/yarn.lock index 33b95e7a2..fb3749a56 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2464,6 +2464,14 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +aphrodite@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/aphrodite/-/aphrodite-0.5.0.tgz#a4b9a8902662395d2702e70ac7a2b4ca66f25703" + integrity sha1-pLmokCZiOV0nAucKx6K0ymbyVwM= + dependencies: + asap "^2.0.3" + inline-style-prefixer "^2.0.0" + apollo-boost@^0.4.4: version "0.4.7" resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.4.7.tgz#b0680ab0893e3f8b1ab1058dcfa2b00cb6440d79" @@ -2705,7 +2713,7 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: +asap@^2.0.0, asap@^2.0.3, asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -3115,6 +3123,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bowser@^1.0.0: + version "1.9.4" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" + integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4652,6 +4665,13 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + dom-helpers@^5.1.0: version "5.1.3" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" @@ -5274,6 +5294,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exenv@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -6354,6 +6379,11 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +hyphenate-style-name@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" + integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== + i18next@^19.3.2: version "19.3.2" resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.3.2.tgz#a17c3c8bb0dd2d8c4a8963429df99730275b3282" @@ -6517,6 +6547,14 @@ ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +inline-style-prefixer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz#c153c7e88fd84fef5c602e95a8168b2770671fe7" + integrity sha1-wVPH6I/YT+9cYC6VqBaLJ3BnH+c= + dependencies: + bowser "^1.0.0" + hyphenate-style-name "^1.0.1" + inquirer@7.0.4, inquirer@^7.0.0: version "7.0.4" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703" @@ -10746,6 +10784,14 @@ react-error-overlay@^6.0.6: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.6.tgz#ac4d9dc4c1b5c536c2c312bf66aa2b09bfa384e2" integrity sha512-Yzpno3enVzSrSCnnljmr4b/2KUQSMZaPuqmS26t9k4nW7uwJk6STWmH9heNjPuvqUTO3jOSPkHoKgO4+Dw7uIw== +react-grid-gallery@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/react-grid-gallery/-/react-grid-gallery-0.5.5.tgz#1b3f3c23a190834e587ab613c96d53ec3af4f0a2" + integrity sha512-DkKg2/Am+VZPDG39fazelTcsZSQrfM/YllnIcWToyUEfOZcrzHxUoqCziCkuTPmCuMbHnrjidBFuDbAFgvSnvQ== + dependencies: + prop-types "^15.5.8" + react-images "^0.5.16" + react-grid-layout@^0.18.2: version "0.18.2" resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.18.2.tgz#91a8740d473d256071c56a558b3449c57224689d" @@ -10784,6 +10830,16 @@ react-image-file-resizer@^0.2.1: resolved "https://registry.yarnpkg.com/react-image-file-resizer/-/react-image-file-resizer-0.2.1.tgz#ee081bd41798ff960eea1a56b1a86ba317fecf11" integrity sha512-uvhNj2NKMUraVKIrsmPNZgWn34b7fjEcuWAyMXUrVb06gedNtOalOBxVwXYocd4KnZRFv2/ilmAE4KEzIkj4aA== +react-images@^0.5.16: + version "0.5.19" + resolved "https://registry.yarnpkg.com/react-images/-/react-images-0.5.19.tgz#9339570029e065f9f28a19f03fdb5d9d5aa109d3" + integrity sha512-B3d4W1uFJj+m17K8S65iAyEJShKGBjPk7n7N1YsPiAydEm8mIq9a6CoeQFMY1d7N2QMs6FBCjT9vELyc5jP5JA== + dependencies: + aphrodite "^0.5.0" + prop-types "^15.6.0" + react-scrolllock "^2.0.1" + react-transition-group "2" + react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" @@ -10838,6 +10894,11 @@ react-overlays@^2.0.0-0: uncontrollable "^7.0.0" warning "^4.0.3" +react-prop-toggle@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-prop-toggle/-/react-prop-toggle-1.0.2.tgz#8b0b7e74653606b1427cfcf6c4eaa9198330568e" + integrity sha512-JmerjAXs7qJ959+d0Ygt7Cb2+4fG+n3I2VXO6JO0AcAY1vkRN/JpZKAN67CMXY889xEJcfylmMPhzvf6nWO68Q== + react-redux@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" @@ -10946,6 +11007,14 @@ react-scripts@3.4.0: optionalDependencies: fsevents "2.1.2" +react-scrolllock@^2.0.1: + version "2.0.7" + resolved "https://registry.yarnpkg.com/react-scrolllock/-/react-scrolllock-2.0.7.tgz#3b879e1fe308fc900ab76e226e9be594c41226fd" + integrity sha512-Gzpu8+ulxdYcybAgJOFTXc70xs7SBZDQbZNpKzchZUgLCJKjz6lrgESx6LHHZgfELx1xYL4yHu3kYQGQPFas/g== + dependencies: + exenv "^1.2.2" + react-prop-toggle "^1.0.2" + react-test-renderer@^16.0.0-0: version "16.13.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.0.tgz#39ba3bf72cedc8210c3f81983f0bb061b14a3014" @@ -10956,6 +11025,16 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.19.0" +react-transition-group@2: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" + integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== + dependencies: + dom-helpers "^3.4.0" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + react@^16.13.0: version "16.13.0" resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7" diff --git a/download-images.js b/download-images.js new file mode 100644 index 000000000..6c3332f6d --- /dev/null +++ b/download-images.js @@ -0,0 +1,24 @@ +var JSZip = require("jszip"); +const axios = require("axios"); // to get the images +require("dotenv").config(); + +module.exports.downloadImages = async function(req, res) { + if (process.env.NODE_ENV !== "production") { + console.log("[IMAGES] Incoming Images to Download", req.body); + } + const zip = new JSZip(); + //res.json({ success: true }); + req.body.images.forEach(i => { + axios.get(i).then(r => zip.file(r)); + // const buffer = await response.buffer(); + // zip.file(file, buffer); + }); + // // Set the name of the zip file in the download + res.setHeader("Content-Type", "application/zip"); + + zip.generateAsync({ type: "nodebuffer" }).then( + function(content) { + res.send(content); + }.bind(res) + ); +}; diff --git a/server.js b/server.js index 258d70662..85225ef77 100644 --- a/server.js +++ b/server.js @@ -29,6 +29,9 @@ app.post("/sendemail", sendEmail.sendEmail); // res.json({ success: true }); // }); +var downloadImages = require("./download-images"); +app.get("/downloadImages", downloadImages.downloadImages); + if (process.env.NODE_ENV === "production") { app.use(express.static(path.join(__dirname, "client/build")));