WIP Download Images + Invoice Tables

This commit is contained in:
Patrick Fic
2020-03-06 14:52:47 -08:00
parent 50bc42347a
commit dd0562cae3
11 changed files with 286 additions and 29 deletions

View File

@@ -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",

View File

@@ -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 (
<div style={{ width: "100%", height: " 100%" }}>
<Sdiv>
The Grid.
<ResponsiveReactGridLayout
{...defaultProps}
onBreakpointChange={onBreakpointChange}
width="100%"
onLayoutChange={layout => {
console.log("layout", layout);
setState({ ...state, layout });
}}
>
{state.layout.map((item, index) => {
console.log("item", item);
return (
<Card style={{ width: "100px" }} key={index} data-grid={item}>
<Card style={{ width: "100px" }} key={item.i} data-grid={item}>
A Card {index}
</Card>
);
})}
</ResponsiveReactGridLayout>
</div>
</Sdiv>
);
}

View File

@@ -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) => <span>{record.vendor.name}</span>
},
{
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) => <DateFormatter>{record.date}</DateFormatter>
}
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const rowExpander = record => (
<div style={{ margin: 0 }}>Invoice details</div>
);
return (
<Table
loading={loading}
size="small"
expandedRowRender={rowExpander}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={invoices}
onChange={handleTableChange}
/>
);
}

View File

@@ -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 (
<div>
@@ -37,6 +35,13 @@ export default connect(
>
Enter Invoice
</Button>
{invoicesQuery.error ? (
<AlertComponent message={invoicesQuery.error.message} type="error" />
) : null}
<InvoicesListTableComponent
loading={invoicesQuery.loading}
invoices={invoicesQuery.data ? invoicesQuery.data.invoices : null}
/>
</div>
);
});

View File

@@ -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 <JobsDetailPliComponent job={job} />;
const invoicesQuery = useQuery(QUERY_INVOICES_BY_JOBID, {
variables: { jobid: job.id },
fetchPolicy: "network-only"
});
return <JobsDetailPliComponent job={job} invoicesQuery={invoicesQuery} />;
}

View File

@@ -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 }) {
<Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: "100%" }} src={previewImage} />
</Modal>
<button
onClick={() => {
axios
.get("/downloadImages", {
images: galleryImages.map(i => i.src)
})
.then(r => console.log("r", r));
}}
>
Dl
</button>
<Gallery
images={galleryImages}
onSelectImage={(index, image) => {
console.log("index, image", index, image);
setgalleryImages(
galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g
)
);
}}
></Gallery>
</div>
);
}

View File

@@ -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
}
}
}
`;

View File

@@ -4,8 +4,8 @@ export const generateCdnThumb = key => {
key: key,
edits: {
resize: {
height: 100,
width: 100
height: 150,
width: 150
}
}
});

View File

@@ -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"

24
download-images.js Normal file
View File

@@ -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)
);
};

View File

@@ -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")));