diff --git a/README.md b/README.md index e6d8489..2b6f458 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo ## Available Scripts +Navigate to project directory: + +### `cd invoice_generator` + In the project directory, you can run: ### `npm start` diff --git a/package-lock.json b/package-lock.json index 4103926..a1ec65c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "jspdf": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-pdf": "^7.7.1", @@ -4690,6 +4691,12 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -5660,6 +5667,17 @@ "node": ">= 4.0.0" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -6011,6 +6029,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6231,6 +6258,17 @@ "node-int64": "^0.4.0" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -6354,6 +6392,31 @@ "node": ">=6" } }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -6846,6 +6909,15 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-minimizer-webpack-plugin": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", @@ -7595,6 +7667,12 @@ } ] }, + "node_modules/dompurify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.0.tgz", + "integrity": "sha512-5RXhAXSCrKTqt9pSbobT9PVRX+oPpENplTZqCiK1l0ya+ZOzwo9kqsGLbYRsAhzIiLCwKEy99XKSSrqnRTLVcw==", + "optional": true + }, "node_modules/domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", @@ -8805,6 +8883,11 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9786,6 +9869,19 @@ "node": ">= 12" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -12759,6 +12855,23 @@ "node": ">=0.10.0" } }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -16375,6 +16488,15 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -17079,6 +17201,15 @@ "node": ">=8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -17568,6 +17699,15 @@ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -17863,6 +18003,15 @@ "node": ">=8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -18370,6 +18519,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index deabe8f..a733913 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "jspdf": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-pdf": "^7.7.1", diff --git a/src/components/MainContentForm_functional.jsx b/src/components/MainContentForm_functional.jsx index b90bae8..d5b823c 100644 --- a/src/components/MainContentForm_functional.jsx +++ b/src/components/MainContentForm_functional.jsx @@ -70,6 +70,8 @@ const MainContentForm = (props) => { quantityRatePeriodsIncrementServiceInvoice1s3: '', descriptionIncrementServiceInvoice1s4: '', quantityRatePeriodsIncrementServiceInvoice1s4: '', + descriptionIncrementServiceInvoice1s5: '', + quantityRatePeriodsIncrementServiceInvoice1s5: '', ...props.values, }; diff --git a/src/config.js b/src/config.js index dfe116f..1f1ccf5 100644 --- a/src/config.js +++ b/src/config.js @@ -8,11 +8,11 @@ const getValuesDefaultMyBusiness = { nameMyBusiness: 'Precision And Research Technology Systems Limited', companyNumberMyBusiness: '13587499', emailMyBusiness: 'edward.middletonsmith@gmail.com', - address1MyBusiness: 'Unit 12a', - address2MyBusiness: 'Somers Road', - address3MyBusiness: 'Rugby', - address4MyBusiness: 'Warwickshire', - address5MyBusiness: 'CV22 7DH', + address1MyBusiness: '53 Alfred Green Close', + address2MyBusiness: 'Rugby', + address3MyBusiness: 'Warwickshire', + address4MyBusiness: '', + address5MyBusiness: 'CV22 6DN', }; const getValuesDefaultMyBusinessBank = { nameBankMyBusiness: 'Starling', @@ -55,19 +55,21 @@ const getValuesDefaultInvoice = { rateServiceInvoice1: 23.94, discretisationDurationServiceInvoice1: 'w', quantityDiscretisationDurationServiceInvoice1: 1, - quantityBillingPeriodServiceInvoice1: 4, + quantityBillingPeriodServiceInvoice1: 5, isIncrementalBillingPeriodIdsServiceInvoice1: true, descriptionIncrementalServiceInvoice1: "Contractor services - week ", - idBillingPeriodFirstIncrementalServiceInvoice1: 1, + idBillingPeriodFirstIncrementalServiceInvoice1: 44, - descriptionIncrementServiceInvoice1s1: "Contractor services - week 1", + descriptionIncrementServiceInvoice1s1: "Contractor services - week 44", quantityRatePeriodsIncrementServiceInvoice1s1: 40, - descriptionIncrementServiceInvoice1s2: "Contractor services - week 2", + descriptionIncrementServiceInvoice1s2: "Contractor services - week 45", quantityRatePeriodsIncrementServiceInvoice1s2: 40, - descriptionIncrementServiceInvoice1s3: "Contractor services - week 3", + descriptionIncrementServiceInvoice1s3: "Contractor services - week 46", quantityRatePeriodsIncrementServiceInvoice1s3: 40, - descriptionIncrementServiceInvoice1s4: "Contractor services - week 4", + descriptionIncrementServiceInvoice1s4: "Contractor services - week 47", quantityRatePeriodsIncrementServiceInvoice1s4: 40, + descriptionIncrementServiceInvoice1s5: "Contractor services - week 48", + quantityRatePeriodsIncrementServiceInvoice1s5: 40, }; const objReturn = { getValuesDefaultFormMetadata, getValuesDefaultMyBusiness, getValuesDefaultMyBusinessBank, getValuesDefaultTheirBusiness, getValuesDefaultTheirBusinessContact, getValuesDefaultInvoice }; diff --git a/src/pages/PageInvoiceOrEstimate.jsx b/src/pages/PageInvoiceOrEstimate.jsx index 0b227e7..9c02a71 100644 --- a/src/pages/PageInvoiceOrEstimate.jsx +++ b/src/pages/PageInvoiceOrEstimate.jsx @@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import imageLogo from '../content/images/Logo.png'; import * as shared from '../scripts/shared.js'; +import { jsPDF } from "jspdf"; const PageInvoiceOrEstimate = () => { const canvasRefs = useRef([]); @@ -58,7 +59,7 @@ const PageInvoiceOrEstimate = () => { const heightBankMyBusiness = 420; const heightBillToSite = 560; const heightHeadTable = 800; - const heightRowMax = 1050; // update this !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + const heightRowMax = 1200; // update this !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! const maxRows = Math.floor((heightRowMax - heightHeadTable - heightLine) / (heightLine + spacingLabel)); console.log("maxRows:", maxRows); const xPositionTableColumns = [120, 420, 520, 620]; @@ -77,18 +78,34 @@ const PageInvoiceOrEstimate = () => { const countPages = Math.ceil(props["quantityServices" + typeForm] / maxRows); console.log("countPages:", countPages); const nameFont = "Arial"; + + const downloadPdf = (canvas, index) => { + const pdf = new jsPDF(); + pdf.addImage(canvas.toDataURL("image/png"), "PNG", 0, 0); + pdf.save(`canvas${index}.pdf`); + }; + const renderCanvases = () => { const canvases = []; for (let indexCanvas = 0; indexCanvas < countPages; indexCanvas++) { canvases.push( - (canvasRefs.current[indexCanvas] = ref)} - width={widthA4} - height={heightA4} - style={{ border: '1px solid black', margin: '10px' }} - /> +
+ (canvasRefs.current[indexCanvas] = ref)} + width={widthA4} + height={heightA4} + style={{ border: '1px solid black', margin: '10px' }} + /> + +
); } return canvases; @@ -124,10 +141,10 @@ const PageInvoiceOrEstimate = () => { description: props["descriptionGood" + typeForm + (indexGoodOrService + 1)], quantity: props["quantityGood" + typeForm + (indexGoodOrService + 1)], rate: props["rateGood" + typeForm + (indexGoodOrService + 1)], - subtotal: props["subtotalGood" + typeForm + (indexGoodOrService + 1)], }; + good["subtotal"] = (good.quantity * good.rate).toFixed(2); page.goods.push(good); - page.total += good.subtotal; + page.total += Number(good.subtotal); } if (hasServices) { indexSubservice++; @@ -141,9 +158,9 @@ const PageInvoiceOrEstimate = () => { quantity: props["quantityRatePeriodsIncrementService" + typeForm + (indexGoodOrService + 1) + "s" + (indexSubservice + 1)], rate: props["rateService" + typeForm + (indexGoodOrService + 1)], }; - service["subtotal"] = service.quantity * service.rate; + service["subtotal"] = (service.quantity * service.rate).toFixed(2); page.services.push(service); - page.total += service.subtotal; + page.total += Number(service.subtotal); } } if (indexRow >= maxRows) { @@ -213,7 +230,7 @@ const PageInvoiceOrEstimate = () => { context.font = '12px ' + nameFont; let heightRowTotal = heightHeadTable + (1 + (page.hasGoods ? page.goods.length : page.services.length)) * heightLine; console.log("page total: ", page.total); - context.fillText(currency + page.total, xPositionTableColumns[3] - 10, heightRowTotal); + context.fillText(currency + page.total.toFixed(2), xPositionTableColumns[3] - 10, heightRowTotal); context.fillText('TOTAL:', xPositionTableColumns[3] - borderPage - 10, heightRowTotal); } }