diff --git a/imports/pages/give/history/Layout.js b/imports/pages/give/history/Layout.js index 14f684765..30c173023 100644 --- a/imports/pages/give/history/Layout.js +++ b/imports/pages/give/history/Layout.js @@ -61,6 +61,8 @@ type ILayout = { reloading: boolean, family: [Object], filterTransactions: Function, + onPrintClick: Function, + printLoading: boolean, }; export default ({ @@ -71,6 +73,8 @@ export default ({ reloading, filterTransactions, family, + onPrintClick, + printLoading, }: ILayout) => (
@@ -90,6 +94,28 @@ export default ({
)} + {transactions.length > 0 && ( +
+ {!printLoading && ( +
+ +
+ )} + + {printLoading && ( +
+ +
+ )} +
+ )} + {!transactions.length && ready && !reloading && (

@@ -117,5 +143,40 @@ export default ({ )} {ready && transactions.length && !reloading && !done && }

+ + {/* Print Button */} +
+
+ {!printLoading && ( +
+ +
+ )} + + {printLoading && ( +
+ +
+ )} +
+
+ ); diff --git a/imports/pages/give/history/__tests__/__snapshots__/Layout.js.snap b/imports/pages/give/history/__tests__/__snapshots__/Layout.js.snap index 0417b2899..07232c899 100644 --- a/imports/pages/give/history/__tests__/__snapshots__/Layout.js.snap +++ b/imports/pages/give/history/__tests__/__snapshots__/Layout.js.snap @@ -35,6 +35,40 @@ exports[`Layout renders no transactions if there are none and ready 1`] = `
+
+
+
+ +
+
+
`; @@ -61,6 +95,40 @@ exports[`Layout renders reloading if no transactions and not ready 1`] = `
+
+
+
+ +
+
+
`; @@ -82,6 +150,30 @@ exports[`Layout renders reloading version 1`] = ` } } /> +
+
+ +
+
+
+
+
+ +
+
+
`; @@ -127,6 +253,30 @@ exports[`Layout renders with negative transactions 1`] = ` family={Array []} />
+
+
+ +
+
+
+
+
+ +
+
+
`; @@ -170,6 +354,30 @@ exports[`Layout renders with props 1`] = ` family={Array []} />
+
+
+ +
+
+
+
+
+ +
+
+
`; diff --git a/imports/pages/give/history/__tests__/__snapshots__/index.js.snap b/imports/pages/give/history/__tests__/__snapshots__/index.js.snap index 828ac0f55..4262abc87 100644 --- a/imports/pages/give/history/__tests__/__snapshots__/index.js.snap +++ b/imports/pages/give/history/__tests__/__snapshots__/index.js.snap @@ -5,6 +5,8 @@ exports[`test renders with props 1`] = ` done={false} family={Array []} filterTransactions={[Function]} + onPrintClick={[Function]} + printLoading={false} ready={true} reloading={false} transactions={Array []} /> diff --git a/imports/pages/give/history/index.js b/imports/pages/give/history/index.js index 215f72ff3..a63ea9ce3 100644 --- a/imports/pages/give/history/index.js +++ b/imports/pages/give/history/index.js @@ -1,11 +1,14 @@ // @flow import { Component, PropTypes } from "react"; +import moment from "moment"; import { graphql } from "react-apollo"; import gql from "graphql-tag"; +import fileSaver from "file-saver"; import infiniteScroll from "../../../decorators/infiniteScroll"; import Authorized from "../../../blocks/authorzied"; +import base64ToBlob from "../../../util/base64ToBlob"; import Layout from "./Layout"; @@ -21,9 +24,11 @@ class TemplateWithoutData extends Component { family: PropTypes.array, // eslint-disable-line }), setRightProps: PropTypes.func, + currentVariables: PropTypes.obj, + getPDF: PropTypes.func, } - state = { refetching: false } + state = { refetching: false, printLoading: false } componentWillMount() { this.props.setRightProps({ @@ -39,6 +44,21 @@ class TemplateWithoutData extends Component { }); } + onPrintClick = (e) => { + e.preventDefault(); + + this.setState({ printLoading: true }); + this.props.getPDF(this.props.currentVariables) + .then(({ data: { transactionStatement } }) => { + const blob = base64ToBlob(transactionStatement.file); + this.setState({ printLoading: false }); + fileSaver.saveAs(blob, `${moment().year()} NewSpring Church Giving Summary`); + }) + .catch(() => { + this.setState({ printLoading: false }); + }); + } + render() { const { transactions, @@ -49,6 +69,8 @@ class TemplateWithoutData extends Component { filterTransactions, } = this.props; + const { printLoading } = this.state; + return ( ); } @@ -71,8 +95,27 @@ const FILTER_QUERY = gql` } } `; + const withFilter = graphql(FILTER_QUERY, { name: "filter" }); +const GET_STATEMENT = gql` + mutation GetGivingStatement($limit: Int, $skip: Int, $people: [Int], $start: String, $end: String) { + transactionStatement( + limit: $limit, + skip: $skip, + people: $people, + start: $start, + end: $end + ){ + file + } + } +`; + +const withStatement = graphql(GET_STATEMENT, { + props: ({ mutate }) => ({ getPDF: (variables) => mutate({ variables }) }), +}); + const TRANSACTIONS_QUERY = gql` query GetTransactions($limit: Int, $skip: Int, $people: [Int], $start: String, $end: String) { transactions( @@ -106,6 +149,7 @@ const withTransactions = graphql(TRANSACTIONS_QUERY, { ssr: false, }, props: ({ data }) => ({ + currentVariables: data.variables, transactions: data.transactions || [], loading: data.loading, done: ( @@ -132,9 +176,11 @@ const withTransactions = graphql(TRANSACTIONS_QUERY, { }); const Template = withFilter( - withTransactions( - infiniteScroll()( - TemplateWithoutData + withStatement( + withTransactions( + infiniteScroll()( + TemplateWithoutData + ) ) ) ); diff --git a/imports/util/base64ToBlob.js b/imports/util/base64ToBlob.js new file mode 100644 index 000000000..1f7f31785 --- /dev/null +++ b/imports/util/base64ToBlob.js @@ -0,0 +1,20 @@ + +const decodeBase64 = (string) => atob(string); + +const getLength = (value) => value.length; + +const buildByteArray = (string, stringLength) => { + const buffer = new ArrayBuffer(stringLength); + const array = new Uint8Array(buffer); + for (let i = 0; i < stringLength; i += 1) { array[i] = string.charCodeAt(i); } + return array; +}; + +const createBlob = (byteArray) => new Blob([byteArray], { type: "application/pdf" }); + +export default (base64String) => { + const decodedString = decodeBase64(base64String); + const decodedStringLength = getLength(decodedString); + const byteArray = buildByteArray(decodedString, decodedStringLength); + return byteArray ? createBlob(byteArray) : null; +}; diff --git a/package.json b/package.json index b514f800a..0b3d210e5 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "cheerio": "^0.20.0", "compression": "^1.6.2", "cookie-parser": "^1.4.3", + "file-saver": "^1.3.3", "fibers": "^1.0.15", "google-map-react": "^0.14.8", "graphql-tag": "^0.1.8", diff --git a/stylesheets/icons.css b/stylesheets/icons.css index 6b943a64d..d41ba7b51 100644 --- a/stylesheets/icons.css +++ b/stylesheets/icons.css @@ -141,6 +141,9 @@ .icon-groups:before { content: "\41"; } +.icon-print:before { + content: "\43"; +} .icon-lock:before { content: "\4b"; } diff --git a/yarn.lock b/yarn.lock index cf37a805c..b9b9faa09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3143,6 +3143,10 @@ file-loader@^0.9.0: dependencies: loader-utils "~0.2.5" +file-saver@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.3.tgz#cdd4c44d3aa264eac2f68ec165bc791c34af1232" + filename-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"