From 78c5bb2b672bb37512004efbaf5a86ff822d5f32 Mon Sep 17 00:00:00 2001 From: Jack Neto Date: Sun, 3 Nov 2019 08:04:28 -0500 Subject: [PATCH 1/4] initial commit --- .travis.yml | 2 +- package-lock.json | 203 ++++++++++++++++------- package.json | 274 +++++++++++++++---------------- src/common/LoginButton/index.jsx | 13 +- src/core/Budget/BudgetChart.jsx | 125 +++++++++++--- src/core/Budget/index.jsx | 13 +- src/data/generateSeedData.js | 31 ++++ 7 files changed, 433 insertions(+), 228 deletions(-) create mode 100644 src/data/generateSeedData.js diff --git a/.travis.yml b/.travis.yml index facede0..fbee88c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - 8 + - 11 cache: directories: - node_modules diff --git a/package-lock.json b/package-lock.json index f0c395a..546ccb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1490,9 +1490,9 @@ } }, "@testing-library/jest-dom": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.1.2.tgz", - "integrity": "sha512-fNf2rCfu0dBD4DmpzqR2ibsaFlFojrWI/EuU8LLqv73CzFIMvT2RMq88p5JVRe4DfeNj0mu0MQ5FTG4mQ0qFaA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.0.tgz", + "integrity": "sha512-H61OmRhGPWLrj9emyISx0qjp8jvC9RWyRniuLAq75Ny5XfPiOvWfnY3Wm2Tf0HXusX+PG40I94Gw792IAtSKKg==", "dev": true, "requires": { "@babel/runtime": "^7.5.1", @@ -2097,9 +2097,9 @@ } }, "acorn-jsx": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", - "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==" }, "acorn-walk": { "version": "6.2.0", @@ -5229,9 +5229,9 @@ } }, "date-fns": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.5.1.tgz", - "integrity": "sha512-ZBrQmuaqH9YqIejbgu8f09ki7wdD2JxWsRTZ/+HnnLNmkI56ty0evnWzKY+ihLT0xX5VdUX0vDNZCxJJGKX2+Q==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.6.0.tgz", + "integrity": "sha512-F55YxqRdEfP/eYQmQjLN798v0AwLjmZ8nMBjdQvNwEE3N/zWVrlkkqT+9seBlPlsbkybG4JmWg3Ee3dIV9BcGQ==" }, "date-now": { "version": "0.1.4", @@ -5989,9 +5989,9 @@ } }, "eslint": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz", - "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.6.0.tgz", + "integrity": "sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g==", "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", @@ -6000,9 +6000,9 @@ "debug": "^4.0.1", "doctrine": "^3.0.0", "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.2", + "eslint-utils": "^1.4.3", "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.1", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", @@ -6012,7 +6012,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.4.1", + "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", @@ -6043,6 +6043,14 @@ "uri-js": "^4.2.2" } }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, "import-fresh": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", @@ -6377,9 +6385,9 @@ } }, "eslint-plugin-jest": { - "version": "22.19.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz", - "integrity": "sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg==", + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.20.0.tgz", + "integrity": "sha512-UwHGXaYprxwd84Wer8H7jZS+5C3LeEaU8VD7NqORY6NmPJrs+9Ugbq3wyjqO3vWtSsDaLar2sqEB8COmOZA4zw==", "dev": true, "requires": { "@typescript-eslint/experimental-utils": "^1.13.0" @@ -6469,9 +6477,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.1.2.tgz", - "integrity": "sha512-ZR+AyesAUGxJAyTFlF3MbzeVHAcQTFQt1fFVe5o0dzY/HFoj1dgQDMoIkiM+ltN/HhlHBYX4JpJwYonjxsyQMA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.2.0.tgz", + "integrity": "sha512-jSlnBjV2cmyIeL555H/FbvuSbQ1AtpHjLMHuPrQnt1eVA6lX8yufdygh7AArI2m8ct7ChHGx2uOaCuxq2MUn6g==", "dev": true }, "eslint-scope": { @@ -6497,12 +6505,12 @@ "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==" }, "espree": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz", - "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", "requires": { - "acorn": "^7.0.0", - "acorn-jsx": "^5.0.2", + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", "eslint-visitor-keys": "^1.1.0" }, "dependencies": { @@ -8214,23 +8222,96 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", + "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", + "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", + "requires": { + "type-fest": "^0.5.2" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "figures": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", + "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^5.2.0" + } + } } }, "internal-ip": { @@ -13372,9 +13453,9 @@ } }, "react": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz", - "integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz", + "integrity": "sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13474,14 +13555,25 @@ } }, "react-dom": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz", - "integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz", + "integrity": "sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.16.2" + "scheduler": "^0.17.0" + }, + "dependencies": { + "scheduler": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz", + "integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + } } }, "react-dropzone": { @@ -13540,9 +13632,9 @@ } }, "react-number-format": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.3.0.tgz", - "integrity": "sha512-IAxm26MVyuiG+CCPlrz4KAOmxi/mVidK/3pIm2796qkfNtfF108QBBOBGLIFFRJQHwdDN/HI0beiAXK6mJM4Lw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.3.1.tgz", + "integrity": "sha512-ze6Lp7SGM71Jr0uZhUaN6grthv08NKTfJezS26zAWZq63ubmJ++FOX7ueGSK27EvpT7/e3ce9L3jlqr1YMFS7Q==", "requires": { "prop-types": "^15.7.2" } @@ -13763,15 +13855,15 @@ } }, "react-test-renderer": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.10.2.tgz", - "integrity": "sha512-k9Qzyev6cTIcIfrhgrFlYQAFxh5EEDO6ALNqYqmKsWVA7Q/rUMTay5nD3nthi6COmYsd4ghVYyi8U86aoeMqYQ==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.11.0.tgz", + "integrity": "sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", "react-is": "^16.8.6", - "scheduler": "^0.16.2" + "scheduler": "^0.17.0" } }, "react-transition-group": { @@ -13900,9 +13992,9 @@ } }, "recharts": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.8.3.tgz", - "integrity": "sha512-DS/nVYeONyIopOp7Ib5VFCcJAzm908ddo05hDhWq0H3wmxtnwT6NWm2F2GnFUksZiVTE4r0MgeLzxYR+6hVBPA==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.8.5.tgz", + "integrity": "sha512-tM9mprJbXVEBxjM7zHsIy6Cc41oO/pVYqyAsOHLxlJrbNBuLs0PHB3iys2M+RqCF0//k8nJtZF6X6swSkWY3tg==", "requires": { "classnames": "^2.2.5", "core-js": "^2.6.10", @@ -14562,9 +14654,10 @@ } }, "scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz", + "integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index db51421..1ba2166 100644 --- a/package.json +++ b/package.json @@ -1,141 +1,141 @@ { - "name": "entaxy", - "description": "Your Personal Finance App", - "version": "0.1.0", - "private": true, - "homepage": "https://entaxy.io", - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --coverage", - "coverage": "react-scripts test --coverage --watchAll=false", - "test:ci": "react-scripts test --coverage --watchAll=false", - "eject": "react-scripts eject", - "lint": "eslint src --ext '.js,.jsx' --config .eslintrc", - "lint:fix": "npm run lint --fix", - "deploy:test": "npm run build && aws s3 sync build/ s3://entaxy-test --delete", - "deploy:staging": "npm run build && aws s3 sync build/ s3://staging.entaxy.io --delete", - "postdeploy:staging": "aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths '/*'", - "deploy:production": "npm run build && aws s3 sync build/ s3://entaxy-production --delete", - "postdeploy:production": "aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths '/*'", - "debug": "echo 'chrome://inspect' && npx react-scripts --inspect-brk test --env=jsdom --runInBand --watch" - }, - "dependencies": { - "@material-ui/core": "^4.5.1", - "@material-ui/icons": "^4.5.1", - "@mdi/js": "^4.5.95", - "@mdi/react": "^1.2.1", - "@vx/axis": "0.0.192", - "@vx/curve": "0.0.192", - "@vx/glyph": "0.0.192", - "@vx/gradient": "0.0.192", - "@vx/grid": "0.0.192", - "@vx/group": "0.0.192", - "@vx/legend": "0.0.192", - "@vx/pattern": "0.0.192", - "@vx/point": "0.0.192", - "@vx/responsive": "0.0.192", - "@vx/scale": "0.0.192", - "@vx/shape": "0.0.192", - "@vx/tooltip": "0.0.192", - "big.js": "^5.2.2", - "blockstack": "^19.2.5", - "chroma-js": "^2.0.4", - "classnames": "^2.2.6", - "d3": "^5.12.0", - "d3-array": "^2.3.3", - "d3-sankey": "^0.12.3", - "date-fns": "^2.5.1", - "formik": "^1.5.8", - "jsdom": "^15.2.0", - "localforage": "^1.7.3", - "lodash": "^4.17.15", - "papaparse": "^5.1.0", - "pluralize": "^8.0.0", - "prop-types": "^15.7.2", - "query-string": "^6.8.3", - "react": "^16.10.2", - "react-confirm": "^0.1.18", - "react-dom": "^16.10.2", - "react-dropzone": "^10.1.10", - "react-motion": "^0.5.2", - "react-number-format": "^4.3.0", - "react-redux": "^7.1.1", - "react-router-dom": "^5.1.2", - "react-scripts": "^3.2.0", - "react-select": "^3.0.8", - "react-virtualized": "^9.21.1", - "react-window": "^1.8.5", - "recharts": "^1.8.3", - "recompose": "^0.30.0", - "redux": "^4.0.4", - "redux-devtools-extension": "^2.13.8", - "redux-logger": "^3.0.6", - "redux-persist": "^6.0.0", - "redux-thunk": "^2.3.0", - "reselect": "^4.0.0", - "uuid": "^3.3.3", - "yup": "^0.27.0" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" + "name": "entaxy", + "description": "Your Personal Finance App", + "version": "0.1.0", + "private": true, + "homepage": "https://entaxy.io", + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --coverage", + "coverage": "react-scripts test --coverage --watchAll=false", + "test:ci": "react-scripts test --coverage --watchAll=false", + "eject": "react-scripts eject", + "lint": "eslint src --ext '.js,.jsx' --config .eslintrc", + "lint:fix": "npm run lint --fix", + "deploy:test": "npm run build && aws s3 sync build/ s3://entaxy-test --delete", + "deploy:staging": "npm run build && aws s3 sync build/ s3://staging.entaxy.io --delete", + "postdeploy:staging": "aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths '/*'", + "deploy:production": "npm run build && aws s3 sync build/ s3://entaxy-production --delete", + "postdeploy:production": "aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths '/*'", + "debug": "echo 'chrome://inspect' && npx react-scripts --inspect-brk test --env=jsdom --runInBand --watch" + }, + "dependencies": { + "@material-ui/core": "^4.5.1", + "@material-ui/icons": "^4.5.1", + "@mdi/js": "^4.5.95", + "@mdi/react": "^1.2.1", + "@vx/axis": "0.0.192", + "@vx/curve": "0.0.192", + "@vx/glyph": "0.0.192", + "@vx/gradient": "0.0.192", + "@vx/grid": "0.0.192", + "@vx/group": "0.0.192", + "@vx/legend": "0.0.192", + "@vx/pattern": "0.0.192", + "@vx/point": "0.0.192", + "@vx/responsive": "0.0.192", + "@vx/scale": "0.0.192", + "@vx/shape": "0.0.192", + "@vx/tooltip": "0.0.192", + "big.js": "^5.2.2", + "blockstack": "^19.2.5", + "chroma-js": "^2.0.4", + "classnames": "^2.2.6", + "d3": "^5.12.0", + "d3-array": "^2.3.3", + "d3-sankey": "^0.12.3", + "date-fns": "^2.6.0", + "formik": "^1.5.8", + "jsdom": "^15.2.0", + "localforage": "^1.7.3", + "lodash": "^4.17.15", + "papaparse": "^5.1.0", + "pluralize": "^8.0.0", + "prop-types": "^15.7.2", + "query-string": "^6.8.3", + "react": "^16.11.0", + "react-confirm": "^0.1.18", + "react-dom": "^16.11.0", + "react-dropzone": "^10.1.10", + "react-motion": "^0.5.2", + "react-number-format": "^4.3.1", + "react-redux": "^7.1.1", + "react-router-dom": "^5.1.2", + "react-scripts": "^3.2.0", + "react-select": "^3.0.8", + "react-virtualized": "^9.21.1", + "react-window": "^1.8.5", + "recharts": "^1.8.5", + "recompose": "^0.30.0", + "redux": "^4.0.4", + "redux-devtools-extension": "^2.13.8", + "redux-logger": "^3.0.6", + "redux-persist": "^6.0.0", + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0", + "uuid": "^3.3.3", + "yup": "^0.27.0" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], + "devDependencies": { + "@testing-library/jest-dom": "^4.2.0", + "@testing-library/react": "^9.3.0", + "coveralls": "^3.0.7", + "enzyme": "^3.10.0", + "enzyme-adapter-react-16": "^1.15.1", + "eslint": "^6.6.0", + "eslint-config-airbnb": "^18.0.1", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-jest": "^22.20.0", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.16.0", + "eslint-plugin-react-hooks": "^2.2.0", + "history": "^4.10.1", + "http-proxy-middleware": "^0.20.0", + "husky": "^3.0.9", + "jest-enzyme": "^7.1.1", + "lint-staged": "^9.4.2", + "react-test-renderer": "^16.11.0", + "redux-mock-store": "^1.5.3" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{js,jsx}": [ + "npm run lint --fix", + "react-scripts test --env=jsdom --bail --coverage --findRelatedTests --watchAll=false", + "git add" + ] + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{js,jsx,mjs}", + "!src/registerServiceWorker.js", + "!src/setupProxy.js", + "!src/store/**/types.js", + "!src/store/transactions/selectors.js", + "!src/core/Taxes/*", + "!src/core/Portfolios/*" ], - "devDependencies": { - "@testing-library/jest-dom": "^4.1.2", - "@testing-library/react": "^9.3.0", - "coveralls": "^3.0.7", - "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.15.1", - "eslint": "^6.5.1", - "eslint-config-airbnb": "^18.0.1", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.19.0", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.1.2", - "history": "^4.10.1", - "http-proxy-middleware": "^0.20.0", - "husky": "^3.0.9", - "jest-enzyme": "^7.1.1", - "lint-staged": "^9.4.2", - "react-test-renderer": "^16.10.2", - "redux-mock-store": "^1.5.3" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "*.{js,jsx}": [ - "npm run lint --fix", - "react-scripts test --env=jsdom --bail --coverage --findRelatedTests --watchAll=false", - "git add" - ] - }, - "jest": { - "collectCoverageFrom": [ - "src/**/*.{js,jsx,mjs}", - "!src/registerServiceWorker.js", - "!src/setupProxy.js", - "!src/store/**/types.js", - "!src/store/transactions/selectors.js", - "!src/core/Taxes/*", - "!src/core/Portfolios/*" - ], - "coverageThreshold": { - "global": { - "branches": 50, - "functions": 50, - "lines": 50, - "statements": 50 - } - } + "coverageThreshold": { + "global": { + "branches": 50, + "functions": 50, + "lines": 50, + "statements": 50 + } } + } } diff --git a/src/common/LoginButton/index.jsx b/src/common/LoginButton/index.jsx index b8583ee..33494e7 100644 --- a/src/common/LoginButton/index.jsx +++ b/src/common/LoginButton/index.jsx @@ -1,5 +1,5 @@ import React, { useState, useRef } from 'react' -import { useSelector } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' import { makeStyles } from '@material-ui/core/styles' import Button from '@material-ui/core/Button' import Avatar from '@material-ui/core/Avatar' @@ -19,7 +19,7 @@ import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown' import LinkTo from '../LinkTo' import { userLogout } from '../../store' import packageJson from '../../../package.json' - +import generateSeedData from '../../data/generateSeedData' const useStyles = makeStyles((theme) => ({ root: { @@ -39,6 +39,7 @@ const useStyles = makeStyles((theme) => ({ const LoginButton = () => { const classes = useStyles() + const dispatch = useDispatch() const user = useSelector((state) => state.user) const [open, setOpen] = useState(false) const anchorRef = useRef(null) @@ -53,6 +54,11 @@ const LoginButton = () => { } setOpen(false) } + const handleGererateSeedData = () => { + setOpen(false) + dispatch(generateSeedData()) + } + if (!user.isAuthenticatedWith) return null return (
@@ -93,6 +99,9 @@ const LoginButton = () => { + + + Version  diff --git a/src/core/Budget/BudgetChart.jsx b/src/core/Budget/BudgetChart.jsx index aec9b9e..f17d5f6 100644 --- a/src/core/Budget/BudgetChart.jsx +++ b/src/core/Budget/BudgetChart.jsx @@ -1,5 +1,10 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/no-multi-comp */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import React, { useState } from 'react' import { useSelector } from 'react-redux' +import { makeStyles } from '@material-ui/core/styles' import { format, startOfMonth } from 'date-fns' import { LineChart, @@ -9,11 +14,35 @@ import { YAxis, Tooltip, Legend, - ResponsiveContainer + ResponsiveContainer, + Surface, + Symbols } from 'recharts' import { currencyFormatter } from '../../util/stringFormatter' +const useStyles = makeStyles((theme) => ({ + legend: { + height: 'calc(100% - 30px)', + overflowY: 'scroll' + }, + legendItem: { + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1), + cursor: 'pointer', + '&:hover': { + background: theme.palette.action.hover + } + }, + legendItemMuted: { + opacity: 0.3 + }, + legendLabel: { + paddingLeft: theme.spacing(1) + } +})) + const BudgetChart = () => { + const classes = useStyles() const { settings, transactions, @@ -35,17 +64,23 @@ const BudgetChart = () => { ) ].filter((category) => !budget.categoriesById[category.parentId].isIncome) - const [opacity, setOpacity] = useState(usedCategories.reduce( - (res, cat) => ({ ...res, [cat]: 1 }), + const [hoverCategory, setHoverCategory] = useState(null) + const [categories, setCategories] = useState(usedCategories.reduce( + (res, cat) => ({ ...res, [cat.name]: true }), {} )) const byMonth = {} + const byMonth = useCallback(() => { + doSomething(a, b); + }, [categorizedTransactions]) + categorizedTransactions.forEach((transaction) => { + console.log('byMnth') const dateKey = startOfMonth(transaction.createdAt).getTime() const category = budget.categoriesById[transaction.categoryId] const group = budget.categoriesById[category.parentId] - if (!group.isIncome) { + if (!group.isIncome && categories[category.name]) { if (byMonth[dateKey] === undefined) { byMonth[dateKey] = usedCategories.reduce((res, cat) => ({ ...res, [cat.name]: 0 }), {}) } @@ -66,33 +101,81 @@ const BudgetChart = () => { left: 20 } - const handleMouseEnter = (obj) => { - setOpacity( - usedCategories.reduce((res, category) => ({ - ...res, - [category.name]: category.name === obj.dataKey ? 1 : 0.3 - }), {}) - ) + const handleMouseEnter = (dataKey) => { + setHoverCategory(dataKey) + // setCategories( + // usedCategories.reduce((res, category) => ({ + // ...res, + // [category.name]: { + // ...categories[category.name], + // opacity: category.name === dataKey ? 1 : 0.3 + // } + // }), {}) + // ) } + const handleMouseLeave = () => { - setOpacity( - usedCategories.reduce((res, category) => ({ - ...res, - [category.name]: 1 - }), {}) + setHoverCategory(null) + + // setCategories( + // usedCategories.reduce((res, category) => ({ + // ...res, + // [category.name]: { + // ...categories[category.name], + // opacity: 1 + // } + // }), {}) + // ) + } + + const handleClick = (dataKey) => { + setCategories((prevState) => ({ + ...prevState, + [dataKey]: { ...prevState[dataKey], selected: !prevState[dataKey].selected } + })) + } + + // const handleMouseOver = (e) => { + // console.log(e) + // } + + const renderCustomLegend = ({ payload }) => { + return ( +
+ {payload.map((entry) => { + const { dataKey, color } = entry + return ( +
handleClick(dataKey)} + className={[classes.legendItem, categories[dataKey] ? null : classes.legendItemMuted].join(' ')} + > + + + + {dataKey} +
+ ) + })} +
+ ) + } + + const renderCustomTooltip = (props) => { + return ( +
test
) } return ( - { usedCategories.map((category) => ( + {usedCategories.map((category) => ( ))} @@ -101,14 +184,14 @@ const BudgetChart = () => { [formatCurrency(value), name]} + content={renderCustomTooltip} /> diff --git a/src/core/Budget/index.jsx b/src/core/Budget/index.jsx index 4705766..f631f09 100644 --- a/src/core/Budget/index.jsx +++ b/src/core/Budget/index.jsx @@ -1,5 +1,4 @@ import React from 'react' -import { useSelector } from 'react-redux' import { makeStyles } from '@material-ui/core/styles' import Container from '@material-ui/core/Container' import Paper from '@material-ui/core/Paper' @@ -23,9 +22,6 @@ const useStyles = makeStyles((theme) => ({ const BudgetIndex = () => { const classes = useStyles() - const { budget } = useSelector((state) => ({ budget: state.budget })) - - const userHasBudget = Object.keys(budget.rules).length > 0 return ( @@ -34,14 +30,7 @@ const BudgetIndex = () => { Budget history - {!userHasBudget && ( - - Not enough data to generate chart - - )} - {userHasBudget && ( - - )} + diff --git a/src/data/generateSeedData.js b/src/data/generateSeedData.js new file mode 100644 index 0000000..4aeaf0d --- /dev/null +++ b/src/data/generateSeedData.js @@ -0,0 +1,31 @@ +import { createAccount } from '../store/accounts/actions' +import { addTransactions } from '../store/transactions/actions' + +const generateSeedData = () => async (dispatch, getState) => { + const { accounts, budget } = getState() + const accountId = await dispatch(createAccount({ + accountType: 'bank', + name: 'Checking', + openingBalance: 0, + institution: 'TD EasyWeb', + currency: 'USD', + currentBalance: { accountCurrency: 0, localCurrency: 0 }, + openingBalanceDate: Date.now() + })) + const account = accounts.byId[accountId] + const categoryIds = Object.keys(budget.categoriesById) + const transactions = [...Array(120).keys()].map((key) => { + const categoryId = categoryIds[Math.floor(Math.random() * categoryIds.length)] + return { + accountId, + description: `Transaction ${key}`, + amount: { accountCurrency: -(Math.random() * 100) + 1 }, + createdAt: new Date(`2019-${1 + (key % 12)}-${Math.floor(Math.random() * 29) + 1}`).getTime(), + categoryId: 'parentId' in budget.categoriesById[categoryId] ? categoryId : undefined + } + }) + + dispatch(addTransactions(account, transactions)) +} + +export default generateSeedData From 02a277651c1b8345ce968cff9f03ff51beb0d1de Mon Sep 17 00:00:00 2001 From: Jack Neto Date: Mon, 11 Nov 2019 18:39:13 -0500 Subject: [PATCH 2/4] updated history chart --- package-lock.json | 26 +++ package.json | 1 + src/common/PopupDateRangePicker/index.jsx | 74 +++++++ .../PopupDateRangePicker/staticRanges.js | 81 +++++++ src/core/Budget/BudgetChart.jsx | 201 ------------------ .../Budget/BudgetLineChart/CustomLegend.jsx | 87 ++++++++ .../Budget/BudgetLineChart/CustomTooltip.jsx | 65 ++++++ src/core/Budget/BudgetLineChart/index.jsx | 94 ++++++++ src/core/Budget/index.jsx | 171 ++++++++++++++- src/data/generateSeedData.js | 5 +- 10 files changed, 595 insertions(+), 210 deletions(-) create mode 100644 src/common/PopupDateRangePicker/index.jsx create mode 100644 src/common/PopupDateRangePicker/staticRanges.js delete mode 100644 src/core/Budget/BudgetChart.jsx create mode 100644 src/core/Budget/BudgetLineChart/CustomLegend.jsx create mode 100644 src/core/Budget/BudgetLineChart/CustomTooltip.jsx create mode 100644 src/core/Budget/BudgetLineChart/index.jsx diff --git a/package-lock.json b/package-lock.json index 546ccb5..f5c9a5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13500,6 +13500,24 @@ "resolved": "https://registry.npmjs.org/react-confirm/-/react-confirm-0.1.18.tgz", "integrity": "sha512-fHLvVj4uFrxQofEuJCQmQ7zRxANk6plyM9iWAHdnAml0o2ohR8oZNgYUoo9iSghRiW/KkVwRJoRBIaPEsAjlVw==" }, + "react-date-range": { + "version": "1.0.0-beta", + "resolved": "https://registry.npmjs.org/react-date-range/-/react-date-range-1.0.0-beta.tgz", + "integrity": "sha512-04xUpkD9qTq6bFpLqcYTvptw+yFl793o1YB+uCWI+SrIp0eyEE4Aw+R9jaWp5cGnxsRDUqywV0cRd8e1Ys2uYQ==", + "requires": { + "classnames": "^2.2.1", + "date-fns": "2.0.0-alpha.7", + "prop-types": "^15.5.10", + "react-list": "^0.8.8" + }, + "dependencies": { + "date-fns": { + "version": "2.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-alpha.7.tgz", + "integrity": "sha1-JFrRb5V2Tqur+ywKQf1dAzwg5Xo=" + } + } + }, "react-dev-utils": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-9.1.0.tgz", @@ -13614,6 +13632,14 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-list": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/react-list/-/react-list-0.8.13.tgz", + "integrity": "sha512-6iY1JdlfHNF7tTuDZMcDf1cQZv5IuLkYJraiEY11CbTi/zASUxmVA8ClCNjuY1gA2l6+g6CNel031BhuXCN0uA==", + "requires": { + "prop-types": "15" + } + }, "react-motion": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", diff --git a/package.json b/package.json index 1ba2166..22ef7b7 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "query-string": "^6.8.3", "react": "^16.11.0", "react-confirm": "^0.1.18", + "react-date-range": "^1.0.0-beta", "react-dom": "^16.11.0", "react-dropzone": "^10.1.10", "react-motion": "^0.5.2", diff --git a/src/common/PopupDateRangePicker/index.jsx b/src/common/PopupDateRangePicker/index.jsx new file mode 100644 index 0000000..973affc --- /dev/null +++ b/src/common/PopupDateRangePicker/index.jsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import Button from '@material-ui/core/Button' +import Popover from '@material-ui/core/Popover' +import CalendarTodayIcon from '@material-ui/icons/CalendarToday' +import { DateRangePicker } from 'react-date-range' +import staticRanges from './staticRanges' + +const PopupDateRangePicker = ({ + children, + ranges, + onChange, + minDate, + maxDate +}) => { + const [anchorEl, setAnchorEl] = useState(null) + + const handleOpenCalendar = (event) => { + setAnchorEl(event.currentTarget) + } + + const handleCloseCalendar = () => { + setAnchorEl(null) + } + + const handleSelectDate = (range) => { + setAnchorEl(null) + onChange(range) + } + + const open = Boolean(anchorEl) + const id = open ? 'simple-popover' : undefined + + return ( + <> + + + + + + ) +} + + +PopupDateRangePicker.propTypes = { + children: PropTypes.node.isRequired, + ranges: PropTypes.array.isRequired, + onChange: PropTypes.func.isRequired, + minDate: PropTypes.object.isRequired, + maxDate: PropTypes.object.isRequired +} + +export default PopupDateRangePicker diff --git a/src/common/PopupDateRangePicker/staticRanges.js b/src/common/PopupDateRangePicker/staticRanges.js new file mode 100644 index 0000000..6ff721e --- /dev/null +++ b/src/common/PopupDateRangePicker/staticRanges.js @@ -0,0 +1,81 @@ +import { createStaticRanges } from 'react-date-range' +import { + addDays, + endOfDay, + startOfDay, + startOfMonth, + endOfMonth, + addMonths, + startOfWeek, + endOfWeek +} from 'date-fns' + +const defineds = { + startOfWeek: startOfWeek(new Date()), + endOfWeek: endOfWeek(new Date()), + startOfLastWeek: startOfWeek(addDays(new Date(), -7)), + endOfLastWeek: endOfWeek(addDays(new Date(), -7)), + startOfToday: startOfDay(new Date()), + endOfToday: endOfDay(new Date()), + startOfYesterday: startOfDay(addDays(new Date(), -1)), + endOfYesterday: endOfDay(addDays(new Date(), -1)), + startOfMonth: startOfMonth(new Date()), + endOfMonth: endOfMonth(new Date()), + startOfLastMonth: startOfMonth(addMonths(new Date(), -1)), + endOfLastMonth: endOfMonth(addMonths(new Date(), -1)), + startOfLastQuarter: startOfMonth(addMonths(new Date(), -3)), + startOfLastSemester: startOfMonth(addMonths(new Date(), -6)), + startOfLastYear: startOfMonth(addMonths(new Date(), -12)) +} + +export default createStaticRanges([ + { + label: 'This Week', + range: () => ({ + startDate: defineds.startOfWeek, + endDate: defineds.endOfWeek + }) + }, + { + label: 'Last Week', + range: () => ({ + startDate: defineds.startOfLastWeek, + endDate: defineds.endOfLastWeek + }) + }, + { + label: 'This Month', + range: () => ({ + startDate: defineds.startOfMonth, + endDate: defineds.endOfMonth + }) + }, + { + label: 'Last Month', + range: () => ({ + startDate: defineds.startOfLastMonth, + endDate: defineds.endOfLastMonth + }) + }, + { + label: 'Last 3 Months', + range: () => ({ + startDate: defineds.startOfLastQuarter, + endDate: defineds.endOfToday + }) + }, + { + label: 'Last 6 Months', + range: () => ({ + startDate: defineds.startOfLastSemester, + endDate: defineds.endOfToday + }) + }, + { + label: 'Last 12 Months', + range: () => ({ + startDate: defineds.startOfLastYear, + endDate: defineds.endOfToday + }) + } +]) diff --git a/src/core/Budget/BudgetChart.jsx b/src/core/Budget/BudgetChart.jsx deleted file mode 100644 index f17d5f6..0000000 --- a/src/core/Budget/BudgetChart.jsx +++ /dev/null @@ -1,201 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/no-multi-comp */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState } from 'react' -import { useSelector } from 'react-redux' -import { makeStyles } from '@material-ui/core/styles' -import { format, startOfMonth } from 'date-fns' -import { - LineChart, - Line, - CartesianGrid, - XAxis, - YAxis, - Tooltip, - Legend, - ResponsiveContainer, - Surface, - Symbols -} from 'recharts' -import { currencyFormatter } from '../../util/stringFormatter' - -const useStyles = makeStyles((theme) => ({ - legend: { - height: 'calc(100% - 30px)', - overflowY: 'scroll' - }, - legendItem: { - paddingLeft: theme.spacing(1), - paddingRight: theme.spacing(1), - cursor: 'pointer', - '&:hover': { - background: theme.palette.action.hover - } - }, - legendItemMuted: { - opacity: 0.3 - }, - legendLabel: { - paddingLeft: theme.spacing(1) - } -})) - -const BudgetChart = () => { - const classes = useStyles() - const { - settings, - transactions, - budget, - formatCurrency - } = useSelector((state) => ({ - settings: state.settings, - transactions: state.transactions, - budget: state.budget, - formatCurrency: currencyFormatter(state.settings.locale, state.settings.currency) - })) - - // Used categories except Income - const categorizedTransactions = transactions.list.filter((transaction) => transaction.categoryId !== undefined) - const usedCategories = [ - ...new Set( - categorizedTransactions - .map((transaction) => budget.categoriesById[transaction.categoryId]) - ) - ].filter((category) => !budget.categoriesById[category.parentId].isIncome) - - const [hoverCategory, setHoverCategory] = useState(null) - const [categories, setCategories] = useState(usedCategories.reduce( - (res, cat) => ({ ...res, [cat.name]: true }), - {} - )) - - const byMonth = {} - const byMonth = useCallback(() => { - doSomething(a, b); - }, [categorizedTransactions]) - - categorizedTransactions.forEach((transaction) => { - console.log('byMnth') - const dateKey = startOfMonth(transaction.createdAt).getTime() - const category = budget.categoriesById[transaction.categoryId] - const group = budget.categoriesById[category.parentId] - if (!group.isIncome && categories[category.name]) { - if (byMonth[dateKey] === undefined) { - byMonth[dateKey] = usedCategories.reduce((res, cat) => ({ ...res, [cat.name]: 0 }), {}) - } - if (byMonth[dateKey][category.name] === undefined) { - byMonth[dateKey][category.name] = 0 - } - byMonth[dateKey][category.name] += -transaction.amount.localCurrency - } - }) - const data = Object.keys(byMonth).sort((a, b) => a - b).map((date) => ({ - date: format(parseInt(date, 10), 'MMM yyyy'), - ...byMonth[date] - })) - const margin = { - top: 5, - right: 20, - bottom: 5, - left: 20 - } - - const handleMouseEnter = (dataKey) => { - setHoverCategory(dataKey) - // setCategories( - // usedCategories.reduce((res, category) => ({ - // ...res, - // [category.name]: { - // ...categories[category.name], - // opacity: category.name === dataKey ? 1 : 0.3 - // } - // }), {}) - // ) - } - - const handleMouseLeave = () => { - setHoverCategory(null) - - // setCategories( - // usedCategories.reduce((res, category) => ({ - // ...res, - // [category.name]: { - // ...categories[category.name], - // opacity: 1 - // } - // }), {}) - // ) - } - - const handleClick = (dataKey) => { - setCategories((prevState) => ({ - ...prevState, - [dataKey]: { ...prevState[dataKey], selected: !prevState[dataKey].selected } - })) - } - - // const handleMouseOver = (e) => { - // console.log(e) - // } - - const renderCustomLegend = ({ payload }) => { - return ( -
- {payload.map((entry) => { - const { dataKey, color } = entry - return ( -
handleClick(dataKey)} - className={[classes.legendItem, categories[dataKey] ? null : classes.legendItemMuted].join(' ')} - > - - - - {dataKey} -
- ) - })} -
- ) - } - - const renderCustomTooltip = (props) => { - return ( -
test
- ) - } - - return ( - - - {usedCategories.map((category) => ( - - ))} - - - - [formatCurrency(value), name]} - content={renderCustomTooltip} - /> - - - - ) -} - -export default BudgetChart diff --git a/src/core/Budget/BudgetLineChart/CustomLegend.jsx b/src/core/Budget/BudgetLineChart/CustomLegend.jsx new file mode 100644 index 0000000..c2a1281 --- /dev/null +++ b/src/core/Budget/BudgetLineChart/CustomLegend.jsx @@ -0,0 +1,87 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +import React from 'react' +import { makeStyles } from '@material-ui/core/styles' +import PropTypes from 'prop-types' + +const useStyles = makeStyles((theme) => ({ + legend: { + height: 'calc(100% - 30px)', + overflowY: 'scroll' + }, + legendItem: { + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1), + cursor: 'pointer', + '&:hover': { + background: theme.palette.action.hover + } + }, + legendItemMuted: { + opacity: 0.3 + }, + legendDot: { + width: 10, + height: 10, + borderRadius: '50%', + display: 'inline-block' + }, + legendLabel: { + paddingLeft: theme.spacing(1) + } +})) + +const CustomLegend = ({ + payload, + categories, + handleClick, + onMouseEnter, + onMouseLeave +}) => { + const classes = useStyles() + // const budget = useSelector((state) => state.budget) + + // const groupCategories = () => { + // const grouped = {} + // payload.map((entry) => { + // const category = budget.categoriesById[categories[entry.dataKey].id] + // const group = budget.categoriesById[category.parentId] + // if (!(group.name in grouped)) grouped[group.name] = [] + // grouped[group.name].push(category.name) + // }) + // return grouped + // } + + return ( +
+ {payload.sort((a, b) => a.dataKey.localeCompare(b.dataKey)).map((entry) => { + const { dataKey, color } = entry + return ( +
handleClick(dataKey)} + onMouseEnter={() => onMouseEnter(dataKey)} + onMouseLeave={onMouseLeave} + className={[ + classes.legendItem, + categories[dataKey].selected ? null : classes.legendItemMuted + ].join(' ')} + > + + {dataKey} +
+ ) + })} +
+ ) +} + +CustomLegend.propTypes = { + payload: PropTypes.array.isRequired, + categories: PropTypes.object.isRequired, + handleClick: PropTypes.func.isRequired, + onMouseEnter: PropTypes.func.isRequired, + onMouseLeave: PropTypes.func.isRequired +} + +export default CustomLegend diff --git a/src/core/Budget/BudgetLineChart/CustomTooltip.jsx b/src/core/Budget/BudgetLineChart/CustomTooltip.jsx new file mode 100644 index 0000000..4a6686e --- /dev/null +++ b/src/core/Budget/BudgetLineChart/CustomTooltip.jsx @@ -0,0 +1,65 @@ +import React from 'react' +import { makeStyles } from '@material-ui/core/styles' +import PropTypes from 'prop-types' +import Paper from '@material-ui/core/Paper' +import Typography from '@material-ui/core/Typography' + +const useStyles = makeStyles((theme) => ({ + root: { + padding: theme.spacing(2) + }, + item: { + display: 'flex', + justifyContent: 'space-between' + }, + dot: { + width: 10, + height: 10, + borderRadius: '50%', + display: 'inline-block', + marginRight: theme.spacing(1) + }, + label: { + fontSize: 12, + align: 'left', + flexGrow: 1, + marginRight: theme.spacing(1) + }, + value: { + fontSize: 12, + textAlign: 'right' + } +})) + + +const CustomTootip = (props) => { + const classes = useStyles() + const { label, payload, formatter } = props + + return ( + + {label} + { payload.sort((a, b) => b.value - a.value).map((item) => ( +
+
+
{item.dataKey}
+
{formatter(item.value)}
+
+ ))} + + ) +} + +CustomTootip.propTypes = { + label: PropTypes.string, + payload: PropTypes.array, + formatter: PropTypes.func +} + +CustomTootip.defaultProps = { + label: undefined, + payload: undefined, + formatter: undefined +} + +export default CustomTootip diff --git a/src/core/Budget/BudgetLineChart/index.jsx b/src/core/Budget/BudgetLineChart/index.jsx new file mode 100644 index 0000000..426dd06 --- /dev/null +++ b/src/core/Budget/BudgetLineChart/index.jsx @@ -0,0 +1,94 @@ +/* eslint-disable react/no-multi-comp */ +import React, { useState } from 'react' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { + LineChart, + Line, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts' +import { currencyFormatter } from '../../../util/stringFormatter' +import CustomLegend from './CustomLegend' +import CustomTooltip from './CustomTooltip' + +const BudgetLineChart = ({ data, categories, onLegendClick }) => { + const formatCurrency = useSelector( + (state) => currencyFormatter(state.settings.locale, state.settings.currency) + ) + + const margin = { + top: 5, + right: 20, + bottom: 5, + left: 25 + } + + const [hoverCategory, setHoverCategory] = useState(null) + + const handleMouseEnterLabel = (dataKey) => { + setHoverCategory(dataKey) + } + + const handleMouseLeaveLabel = () => { + setHoverCategory(null) + } + + return ( + <> + + + {Object.values(categories).map((category) => { + let strokeWidth = 2 + if (hoverCategory) { + strokeWidth = hoverCategory === category.name ? 4 : 1 + } + return ( + + ) + })} + + + formatCurrency(value)} /> + [formatCurrency(value), name]} + content={} + /> + ( + + )} + /> + + + + ) +} + +BudgetLineChart.propTypes = { + data: PropTypes.array.isRequired, + categories: PropTypes.object.isRequired, + onLegendClick: PropTypes.func.isRequired +} + +export default BudgetLineChart diff --git a/src/core/Budget/index.jsx b/src/core/Budget/index.jsx index f631f09..b3af2e4 100644 --- a/src/core/Budget/index.jsx +++ b/src/core/Budget/index.jsx @@ -1,10 +1,16 @@ -import React from 'react' +import React, { useReducer } from 'react' +import { useSelector } from 'react-redux' import { makeStyles } from '@material-ui/core/styles' import Container from '@material-ui/core/Container' import Paper from '@material-ui/core/Paper' import Typography from '@material-ui/core/Typography' import Grid from '@material-ui/core/Grid' -import BudgetChart from './BudgetChart' +import 'react-date-range/dist/styles.css' // main style file +import 'react-date-range/dist/theme/default.css' // theme css file +import { format, startOfMonth, subMonths } from 'date-fns' +import BudgetLineChart from './BudgetLineChart' +import PopupDateRangePicker from '../../common/PopupDateRangePicker' +import { liabilityAccounts } from '../../store/accounts/reducer' const useStyles = makeStyles((theme) => ({ root: { @@ -15,22 +21,173 @@ const useStyles = makeStyles((theme) => ({ height: 400, minWidth: 200 }, - chartTitle: { - padding: theme.spacing(1) + pageHeader: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'space-between' + }, + filters: { + display: 'flex', + justifyContent: 'flex-end' } })) const BudgetIndex = () => { const classes = useStyles() + const { + accounts, + transactions, + budgetCategoriesById + } = useSelector((state) => ({ + accounts: state.accounts.byId, + transactions: state.transactions.list.filter((transaction) => ( + transaction.categoryId !== undefined + && !state.budget.categoriesById[transaction.categoryId].isIncome + )).sort((a, b) => a.createdAt - b.createdAt), + budgetCategoriesById: state.budget.categoriesById + })) + + + const filterTransactions = ({ startDate, endDate }) => { + const usedCategories = {} + const filteredTransactions = transactions.filter((transaction) => { + const category = budgetCategoriesById[transaction.categoryId] + const group = budgetCategoriesById[category.parentId] + if ( + !group.isIncome + && transaction.createdAt >= startDate.getTime() + && transaction.createdAt <= endDate.getTime() + ) { + usedCategories[category.name] = { ...category, selected: true } + return true + } + return false + }) + return { filteredTransactions, usedCategories } + } + + const aggregateData = (filteredTransactions, usedCategories) => { + const byPeriod = {} + filteredTransactions.forEach((transaction) => { + const date = startOfMonth(transaction.createdAt).getTime() + const category = budgetCategoriesById[transaction.categoryId] + const { accountType } = accounts[transaction.accountId] + if (usedCategories[category.name].selected) { + if (byPeriod[date] === undefined) { + // initialize entries for this date + byPeriod[date] = Object.values(usedCategories).sort().reduce( + (res, cat) => { + if (cat.selected) return { ...res, [cat.name]: 0 } + return res + }, {} + ) + } + if (byPeriod[date][category.name] === undefined) { + byPeriod[date][category.name] = 0 + } + if (accountType in Object.keys(liabilityAccounts)) { + byPeriod[date][category.name] += transaction.amount.localCurrency + } else { + byPeriod[date][category.name] -= transaction.amount.localCurrency + } + } + }) + + return Object.keys(byPeriod).sort((a, b) => a - b).map((date) => ({ + date: format(parseInt(date, 10), 'MMM yyyy'), + ...byPeriod[date] + })) + } + + const init = (prevState = {}, newDateRange = null) => { + let dateRange + if (!newDateRange) { + const minDate = transactions.length > 0 ? new Date(transactions[0].createdAt) : new Date() + const maxDate = transactions.length > 0 ? new Date(transactions[transactions.length - 1].createdAt) : new Date() + dateRange = { + minDate, + maxDate, + startDate: subMonths(startOfMonth(maxDate), 3), + endDate: maxDate, + key: 'selection' + } + } else { + dateRange = { + ...prevState.dateRange, + startDate: newDateRange.startDate, + endDate: newDateRange.endDate + } + } + + const { filteredTransactions, usedCategories } = filterTransactions(dateRange) + return { + dateRange, + filteredTransactions, + usedCategories, + data: aggregateData(filteredTransactions, usedCategories) + } + } + + const reducer = (state, { type, payload }) => { + switch (type) { + case 'UPDATE_DATE_RANGE': { + return init(state, payload) + } + case 'TOGGLE_CATEGORY': { + const usedCategories = { + ...state.usedCategories, + [payload]: { + ...state.usedCategories[payload], + selected: !state.usedCategories[payload].selected + } + } + return { + ...state, + usedCategories, + data: aggregateData(state.filteredTransactions, usedCategories) + } + } + default: + throw new Error() + } + } + + const [state, dispatch] = useReducer(reducer, init()) + + const handleLegendClick = (dataKey) => { + dispatch({ type: 'TOGGLE_CATEGORY', payload: dataKey }) + } + + const handleSelectDate = (ranges) => { + dispatch({ type: 'UPDATE_DATE_RANGE', payload: ranges.selection }) + } + return ( - - + + Budget history +
+ + {format(state.dateRange.startDate, 'MMM dd, yyyy')} -  + {format(state.dateRange.endDate, 'MMM dd, yyyy')} + +
+
+ - +
diff --git a/src/data/generateSeedData.js b/src/data/generateSeedData.js index 4aeaf0d..fd3cd69 100644 --- a/src/data/generateSeedData.js +++ b/src/data/generateSeedData.js @@ -2,16 +2,17 @@ import { createAccount } from '../store/accounts/actions' import { addTransactions } from '../store/transactions/actions' const generateSeedData = () => async (dispatch, getState) => { - const { accounts, budget } = getState() const accountId = await dispatch(createAccount({ accountType: 'bank', name: 'Checking', openingBalance: 0, institution: 'TD EasyWeb', - currency: 'USD', + currency: getState().settings.currency, currentBalance: { accountCurrency: 0, localCurrency: 0 }, openingBalanceDate: Date.now() })) + + const { accounts, budget } = getState() const account = accounts.byId[accountId] const categoryIds = Object.keys(budget.categoriesById) const transactions = [...Array(120).keys()].map((key) => { From 96b6e82109b74e518f88da6ddaaf543ade4f3983 Mon Sep 17 00:00:00 2001 From: Jack Neto Date: Thu, 14 Nov 2019 09:33:12 -0500 Subject: [PATCH 3/4] Add datepicker; Update history and flow charts --- src/common/LoginButton/index.jsx | 2 +- src/common/PopupDateRangePicker/index.jsx | 2 +- .../PopupDateRangePicker/staticRanges.js | 9 +- src/core/Budget/MoneyFlow.jsx | 203 +++++++----------- .../Budget/__tests__/BudgetChart.test.jsx | 25 --- .../__snapshots__/index.test.jsx.snap | 79 +++++-- src/core/Budget/__tests__/index.test.jsx | 8 +- 7 files changed, 155 insertions(+), 173 deletions(-) delete mode 100644 src/core/Budget/__tests__/BudgetChart.test.jsx diff --git a/src/common/LoginButton/index.jsx b/src/common/LoginButton/index.jsx index 33494e7..3de2d95 100644 --- a/src/common/LoginButton/index.jsx +++ b/src/common/LoginButton/index.jsx @@ -99,7 +99,7 @@ const LoginButton = () => { - + diff --git a/src/common/PopupDateRangePicker/index.jsx b/src/common/PopupDateRangePicker/index.jsx index 973affc..a2843c5 100644 --- a/src/common/PopupDateRangePicker/index.jsx +++ b/src/common/PopupDateRangePicker/index.jsx @@ -55,7 +55,7 @@ const PopupDateRangePicker = ({ minDate={minDate} maxDate={maxDate} inputRanges={[]} - staticRanges={staticRanges} + staticRanges={staticRanges(minDate)} /> diff --git a/src/common/PopupDateRangePicker/staticRanges.js b/src/common/PopupDateRangePicker/staticRanges.js index 6ff721e..2dcfa03 100644 --- a/src/common/PopupDateRangePicker/staticRanges.js +++ b/src/common/PopupDateRangePicker/staticRanges.js @@ -28,7 +28,7 @@ const defineds = { startOfLastYear: startOfMonth(addMonths(new Date(), -12)) } -export default createStaticRanges([ +export default (minDate) => createStaticRanges([ { label: 'This Week', range: () => ({ @@ -77,5 +77,12 @@ export default createStaticRanges([ startDate: defineds.startOfLastYear, endDate: defineds.endOfToday }) + }, + { + label: 'All time', + range: () => ({ + startDate: startOfDay(minDate), + endDate: defineds.endOfToday + }) } ]) diff --git a/src/core/Budget/MoneyFlow.jsx b/src/core/Budget/MoneyFlow.jsx index 69ae6fe..8c55c7f 100644 --- a/src/core/Budget/MoneyFlow.jsx +++ b/src/core/Budget/MoneyFlow.jsx @@ -1,17 +1,13 @@ import React, { useState, useMemo } from 'react' import { makeStyles } from '@material-ui/core/styles' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import Container from '@material-ui/core/Container' import Paper from '@material-ui/core/Paper' import Typography from '@material-ui/core/Typography' import Grid from '@material-ui/core/Grid' -import FormControl from '@material-ui/core/FormControl' -import InputLabel from '@material-ui/core/InputLabel' -import Select from '@material-ui/core/Select' -import MenuItem from '@material-ui/core/MenuItem' -import { startOfMonth, endOfMonth } from 'date-fns' +import { format, startOfMonth, subMonths } from 'date-fns' import SankeyDiagram from './SankeyDiagram' -import { showSnackbar } from '../../store/user/actions' +import PopupDateRangePicker from '../../common/PopupDateRangePicker' const useStyles = makeStyles((theme) => ({ root: { @@ -48,42 +44,38 @@ const useStyles = makeStyles((theme) => ({ const MoneyFlow = () => { const classes = useStyles() - const dispatch = useDispatch() - const { budget, transactions, dateFormatter } = useSelector((state) => ({ - transactions: state.transactions.list.sort((a, b) => a.createdAt - b.createdAt), - budget: state.budget, - dateFormatter: (new Intl.DateTimeFormat(state.settings.locale, { month: 'long' })).format + const { budget, transactions } = useSelector((state) => ({ + transactions: state.transactions.list.filter((transaction) => ( + transaction.categoryId !== undefined + )).sort((a, b) => a.createdAt - b.createdAt), + budget: state.budget })) - const userHasBudget = Object.keys(budget.rules).length > 0 - const start = transactions.length > 0 ? new Date(transactions[0].createdAt) : new Date() - const end = transactions.length > 0 ? new Date(transactions[transactions.length - 1].createdAt) : new Date() - const yearsList = Array.from( - Array(end.getFullYear() - start.getFullYear() + 1).keys(), - (n) => start.getFullYear() + n - ) - const monthsList = Array.from(Array(12).keys(), (n) => n + 1).map( - (month) => dateFormatter(new Date(`${month}/01/1970`).getTime()) - ) - const initialValues = { - fromYear: yearsList[0], - fromMonth: monthsList[start.getMonth()], - toYear: yearsList[yearsList.length - 1], - toMonth: monthsList[end.getMonth()] - } - initialValues.fromDate = startOfMonth(new Date(`${initialValues.fromMonth}/01/${initialValues.fromYear}`)) - initialValues.toDate = endOfMonth(new Date(`${initialValues.toMonth}/01/${initialValues.toYear}`)) + const minDate = transactions.length > 0 ? new Date(transactions[0].createdAt) : new Date() + const maxDate = transactions.length > 0 ? new Date(transactions[transactions.length - 1].createdAt) : new Date() - const [values, setValues] = useState(initialValues) + const [dateRange, setDateRange] = useState({ + minDate, + maxDate, + startDate: subMonths(startOfMonth(maxDate), 3), + endDate: maxDate, + key: 'selection' + }) const graphData = useMemo(() => { const incomeGroupId = budget.categoryTree.find((group) => group.isIncome).id + const { startDate, endDate } = dateRange + const expenseGroup = { + id: 'Expenses', + name: 'Expenses', + index: 1, + total: 0 + } + + const filteredTransactions = transactions.filter((transaction) => ( + transaction.createdAt >= startDate.getTime() && transaction.createdAt <= endDate.getTime() + )) - const filteredTransactions = transactions.filter( - (transaction) => transaction.categoryId !== undefined - && transaction.createdAt >= values.fromDate.getTime() - && transaction.createdAt <= values.toDate.getTime() - ) const usedCategories = Object.values(filteredTransactions.reduce((result, transaction) => { const category = budget.categoriesById[transaction.categoryId] const group = budget.categoriesById[category.parentId] @@ -94,15 +86,15 @@ const MoneyFlow = () => { ...category, index: (result[category.id] === undefined ? index : result[category.id].index), total: result[category.id] === undefined - ? transaction.amount - : result[category.id].total + transaction.amount + ? transaction.amount.localCurrency + : result[category.id].total + transaction.amount.localCurrency }, [group.id]: { ...group, index: (result[group.id] === undefined ? index + 1 : result[group.id].index), total: result[group.id] === undefined - ? transaction.amount - : result[group.id].total + transaction.amount + ? transaction.amount.localCurrency + : result[group.id].total + transaction.amount.localCurrency } } }, { @@ -110,32 +102,38 @@ const MoneyFlow = () => { ...budget.categoriesById[incomeGroupId], index: 0, total: 0 - } + }, + Expense: expenseGroup })) + const incomeCategories = usedCategories.filter((cat) => cat.parentId === incomeGroupId) + const incomeGroup = usedCategories.find((cat) => cat.id === incomeGroupId) + + console.log({ usedCategories }) - const incomeCategories = usedCategories.filter( - (category) => category.parentId === incomeGroupId - ) - const incomeGroup = usedCategories.find( - (category) => category.id === incomeGroupId - ) return usedCategories .sort((a, b) => a.index > b.index) .reduce((data, category) => { const newLinks = [] if (category.id === incomeGroupId) { // Income group - // from income categories + // from income categories to income group incomeCategories.forEach((incomeCategory) => { + expenseGroup.total += Math.abs(incomeCategory.total) newLinks.push({ source: incomeCategory.index, target: category.index, value: Math.abs(incomeCategory.total) }) }) - } else if (category.parentId === undefined) { // Non income groups - // from income group + // from income group to expenses group newLinks.push({ source: incomeGroup.index, + target: 1, + value: expenseGroup.total + }) + } else if (category.parentId === undefined && category.id !== 'Expenses') { // Non income groups + // from expenses group to non income groups + newLinks.push({ + source: expenseGroup.index, target: category.index, value: Math.abs(category.total) }) @@ -154,35 +152,23 @@ const MoneyFlow = () => { ...data, nodes: [ ...data.nodes, - { name: category.name, data: category, isIncome: category.parentId === incomeGroupId } + { + name: category.name, + data: category, + isIncome: category.parentId === incomeGroupId + } ], - links: [ - ...data.links, - ...newLinks - ] + links: [...data.links, ...newLinks] } }, { nodes: [], links: [] }) - }, [budget, transactions, values]) - + }, [budget, transactions, dateRange]) - function handleChange(event) { - setValues((oldValues) => { - const newValues = { - ...oldValues, - [event.target.name]: event.target.value - } - newValues.fromDate = startOfMonth(new Date(`${newValues.fromMonth}/01/${newValues.fromYear}`)) - newValues.toDate = endOfMonth(new Date(`${newValues.toMonth}/01/${newValues.toYear}`)) - if (newValues.fromDate.getTime() < newValues.toDate.getTime() - || `${newValues.fromMonth}/${newValues.fromYear}` === `${newValues.toMonth}/${newValues.toYear}`) { - return newValues - } - dispatch(showSnackbar({ - text: 'The start date needs to come before the end date', - status: 'error' - })) - return oldValues - }) + const handleSelectDate = (ranges) => { + setDateRange((prevState) => ({ + ...prevState.dateRange, + startDate: ranges.selection.startDate, + endDate: ranges.selection.endDate + })) } return ( @@ -193,70 +179,25 @@ const MoneyFlow = () => { Money flow
- - From - - - Month - - - - Year - - - - To - - - Month - - - - Year - - + + {format(dateRange.startDate, 'MMM dd, yyyy')} -  + {format(dateRange.endDate, 'MMM dd, yyyy')} +
- {(!userHasBudget || graphData.links.length === 0) && ( + {(graphData.links.length === 0) && ( Not enough data to generate chart )} - {userHasBudget && graphData.links.length > 0 && ( + {graphData.links.length > 0 && ( )} diff --git a/src/core/Budget/__tests__/BudgetChart.test.jsx b/src/core/Budget/__tests__/BudgetChart.test.jsx deleted file mode 100644 index 11b3485..0000000 --- a/src/core/Budget/__tests__/BudgetChart.test.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { mount } from 'enzyme' -import { Provider } from 'react-redux' -import configureMockStore from 'redux-mock-store' -import BudgetChart from '../BudgetChart' -import { initialState as transactionsInitialState } from '../../../store/transactions/reducer' -import { initialState as settingsInitialState } from '../../../store/settings/reducer' -import { initialState as budgetInitialState } from '../../../store/budget/reducer' - -describe('BudgetChart', () => { - it('matches snapshot with no used categories', () => { - const mockStore = configureMockStore() - const store = mockStore({ - transactions: transactionsInitialState, - settings: settingsInitialState, - budget: budgetInitialState - }) - const wrapper = mount(( - - - - )) - expect(wrapper.debug()).toMatchSnapshot() - }) -}) diff --git a/src/core/Budget/__tests__/__snapshots__/index.test.jsx.snap b/src/core/Budget/__tests__/__snapshots__/index.test.jsx.snap index b1c66a7..4b0f305 100644 --- a/src/core/Budget/__tests__/__snapshots__/index.test.jsx.snap +++ b/src/core/Budget/__tests__/__snapshots__/index.test.jsx.snap @@ -9,26 +9,79 @@ exports[`BudgetIndex matches snapshot with no accounts 1`] = `
- - -
- - -
+ + +
+ + +
Budget history
+
+ + + + + + + + + + + + + + + + +
+
+
+
+ + +
- - -

- Not enough data to generate chart -

-
-
+ + +
+ + +
+ +
+ +
diff --git a/src/core/Budget/__tests__/index.test.jsx b/src/core/Budget/__tests__/index.test.jsx index e9db376..7da8ff9 100644 --- a/src/core/Budget/__tests__/index.test.jsx +++ b/src/core/Budget/__tests__/index.test.jsx @@ -3,13 +3,19 @@ import { mount } from 'enzyme' import { Provider } from 'react-redux' import configureMockStore from 'redux-mock-store' import BudgetIndex from '..' +import { initialState as accountsInitialState } from '../../../store/accounts/reducer' +import { initialState as transactionsInitialState } from '../../../store/transactions/reducer' import { initialState as budgetInitialState } from '../../../store/budget/reducer' +import { initialState as settingsInitialState } from '../../../store/settings/reducer' describe('BudgetIndex', () => { it('matches snapshot with no accounts', () => { const mockStore = configureMockStore() const store = mockStore({ - budget: budgetInitialState + accounts: accountsInitialState, + transactions: transactionsInitialState, + budget: budgetInitialState, + settings: settingsInitialState }) const wrapper = mount(( From d70001b6a6c8892c803e484aefbd5279b6fa6df1 Mon Sep 17 00:00:00 2001 From: Jack Neto Date: Sun, 17 Nov 2019 13:04:13 -0500 Subject: [PATCH 4/4] Update charts; Add tests --- package-lock.json | 373 ++++----- package.json | 37 +- .../__snapshots__/index.test.jsx.snap | 10 +- src/common/ConfirmDialog/index.jsx | 2 +- .../__snapshots__/index.test.jsx.snap | 118 --- src/common/Header/__tests__/index.test.jsx | 70 +- src/common/Header/index.jsx | 102 +-- .../LoginButton/__tests__/index.test.jsx | 76 +- src/common/LoginButton/index.jsx | 11 +- src/common/PopupDateRangePicker/index.jsx | 1 + .../TransactionDialog.test.jsx.snap | 98 +-- .../TransactionsTable.test.jsx.snap | 29 +- .../Transactions/__tests__/index.test.jsx | 2 - .../__snapshots__/form.test.jsx.snap | 10 +- .../Budget/BudgetLineChart/CustomTooltip.jsx | 2 + src/core/Budget/BudgetLineChart/index.jsx | 92 ++- .../Budget/{index.jsx => HistoryChart.jsx} | 4 +- src/core/Budget/MoneyFlow.jsx | 43 +- src/core/Budget/SankeyDiagram/SankeyNode.jsx | 3 + src/core/Budget/SankeyDiagram/index.jsx | 62 +- .../Budget/__tests__/HistoryChart.test.jsx | 107 +++ src/core/Budget/__tests__/MoneyFlow.test.jsx | 100 ++- .../__snapshots__/BudgetChart.test.jsx.snap | 16 - .../__snapshots__/index.test.jsx.snap | 99 --- src/core/Budget/__tests__/index.test.jsx | 27 - .../__snapshots__/form.test.jsx.snap | 92 +-- .../__snapshots__/index.test.jsx.snap | 748 ------------------ src/core/Dashboard/__tests__/index.test.jsx | 88 +-- src/core/Dashboard/index.jsx | 51 +- .../__snapshots__/form.test.jsx.snap | 8 +- src/routes.jsx | 4 +- src/util/useKeyboardCommand.jsx | 29 + 32 files changed, 871 insertions(+), 1643 deletions(-) delete mode 100644 src/common/Header/__tests__/__snapshots__/index.test.jsx.snap rename src/core/Budget/{index.jsx => HistoryChart.jsx} (99%) create mode 100644 src/core/Budget/__tests__/HistoryChart.test.jsx delete mode 100644 src/core/Budget/__tests__/__snapshots__/BudgetChart.test.jsx.snap delete mode 100644 src/core/Budget/__tests__/__snapshots__/index.test.jsx.snap delete mode 100644 src/core/Budget/__tests__/index.test.jsx delete mode 100644 src/core/Dashboard/__tests__/__snapshots__/index.test.jsx.snap create mode 100644 src/util/useKeyboardCommand.jsx diff --git a/package-lock.json b/package-lock.json index f5c9a5d..772508d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "entaxy", - "version": "0.1.0", + "version": "0.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1218,21 +1218,19 @@ } }, "@material-ui/core": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.5.1.tgz", - "integrity": "sha512-6pyk7diT7bflf4qUpqgPCpKYqjhRHPFwsgEV2Gv71lMqwxuRygFGHE2TdZ+l5T249H66Doj2P/j6fW7yzgxTWw==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.6.1.tgz", + "integrity": "sha512-TljDMCJmi1zh7JhAFTp8qjIlbkVACiNftrcitzJJ+hAqpuP9PTO4euEkkAuYjISfUFZl3Z4kaOrBwN1HDrhIOQ==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.5.0", - "@material-ui/system": "^4.5.0", + "@material-ui/styles": "^4.6.0", + "@material-ui/system": "^4.5.2", "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.4.0", + "@material-ui/utils": "^4.5.2", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.2", "convert-css-length": "^2.0.1", - "deepmerge": "^4.0.0", "hoist-non-react-statics": "^3.2.1", - "is-plain-object": "^3.0.0", "normalize-scroll-left": "^0.2.0", "popper.js": "^1.14.1", "prop-types": "^15.7.2", @@ -1248,17 +1246,16 @@ } }, "@material-ui/styles": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", - "integrity": "sha512-O0NSAECHK9f3DZK6wy56PZzp8b/7KSdfpJs8DSC7vnXUAoMPCTtchBKLzMtUsNlijiJFeJjSxNdQfjWXgyur5A==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.6.0.tgz", + "integrity": "sha512-lqqh4UEMdIYcU1Yth4pQyMTah02uAkg3NOT3MirN9FUexdL8pNA6zCHigEgDSfwmvnXyxHhxTkphfy0DRfnt9w==", "requires": { "@babel/runtime": "^7.4.4", "@emotion/hash": "^0.7.1", "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.1.0", + "@material-ui/utils": "^4.5.2", "clsx": "^1.0.2", "csstype": "^2.5.2", - "deepmerge": "^4.0.0", "hoist-non-react-statics": "^3.2.1", "jss": "^10.0.0", "jss-plugin-camel-case": "^10.0.0", @@ -1272,12 +1269,12 @@ } }, "@material-ui/system": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.5.1.tgz", - "integrity": "sha512-M72CGz3MYxXTFLet2qWmQDBXZdtF7JKGqYaf7t9MPDYD6WYG6wKM2hUbgUtRKOwls8ZBXQGKsiAX8K4v5pXSPw==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.5.2.tgz", + "integrity": "sha512-h9RWvdM9XKlHHqwiuhyvWdobptQkHli+m2jJFs7i1AI/hmGsIc4reDmS7fInhETgt/Txx7uiAIznfRNIIVHmQw==", "requires": { "@babel/runtime": "^7.4.4", - "deepmerge": "^4.0.0", + "@material-ui/utils": "^4.5.2", "prop-types": "^15.7.2" } }, @@ -1290,9 +1287,9 @@ } }, "@material-ui/utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.4.0.tgz", - "integrity": "sha512-UXoQVwArQEQWXxf2FPs0iJGT+MePQpKr0Qh0CPoLc1OdF0GSMTmQczcqCzwZkeHxHAOq/NkIKM1Pb/ih1Avicg==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.5.2.tgz", + "integrity": "sha512-zhbNfHd1gLa8At6RPDG7uMZubHxbY+LtM6IkSfeWi6Lo4Ax80l62YaN1QmUpO1IvGCkn/j62tQX3yObiQZrJsQ==", "requires": { "@babel/runtime": "^7.4.4", "prop-types": "^15.7.2", @@ -1476,23 +1473,23 @@ } }, "@testing-library/dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.5.0.tgz", - "integrity": "sha512-3lQx248dhJzvV2a76F1VaqehX+iquSVVW27caDaLoQZdUHEZjB370n7FO2WoYwOQQ7NB10AvfPhrARYnNgvf1g==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.10.1.tgz", + "integrity": "sha512-5BPKxaO+zSJDUbVZBRNf9KrmDkm/EcjjaHSg3F9+031VZyPACKXlwLBjVzZxheunT9m72DoIq7WvyE457/Xweg==", "dev": true, "requires": { - "@babel/runtime": "^7.5.5", + "@babel/runtime": "^7.6.2", "@sheerun/mutationobserver-shim": "^0.3.2", "@types/testing-library__dom": "^6.0.0", "aria-query": "3.0.0", - "pretty-format": "^24.8.0", - "wait-for-expect": "^1.3.0" + "pretty-format": "^24.9.0", + "wait-for-expect": "^3.0.0" } }, "@testing-library/jest-dom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.0.tgz", - "integrity": "sha512-H61OmRhGPWLrj9emyISx0qjp8jvC9RWyRniuLAq75Ny5XfPiOvWfnY3Wm2Tf0HXusX+PG40I94Gw792IAtSKKg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz", + "integrity": "sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==", "dev": true, "requires": { "@babel/runtime": "^7.5.1", @@ -1507,9 +1504,9 @@ } }, "@testing-library/react": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.3.0.tgz", - "integrity": "sha512-FTPCwmLo0tLtP50Au2uGz4/N1BcJTnBx4StDVHZ47zPMEj1/+J2rk/RTj8SLoHRKWCtcmhN4wRmudOXQNP29/w==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.3.2.tgz", + "integrity": "sha512-J6ftWtm218tOLS175MF9eWCxGp+X+cUXCpkPIin8KAXWtyZbr9CbqJ8M8QNd6spZxJDAGlw+leLG4MJWLlqVgg==", "dev": true, "requires": { "@babel/runtime": "^7.6.0", @@ -1647,18 +1644,18 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" }, "@types/react": { - "version": "16.9.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz", - "integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==", + "version": "16.9.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz", + "integrity": "sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, "@types/react-dom": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz", - "integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==", + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", "dev": true, "requires": { "@types/react": "*" @@ -1678,18 +1675,18 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, "@types/testing-library__dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.5.0.tgz", - "integrity": "sha512-VgfB4rXWA7jAHbmTM4yAZeaxsh/YBd0qIuOZ7v1+17V6poO0KxdH6bq95O2+EiGwuAkS7Lcm1gh5sI7gUjAkxw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.10.0.tgz", + "integrity": "sha512-mL/GMlyQxiZplbUuFNwA0vAI3k3uJNSf6slr5AVve9TXmfLfyefNT0uHHnxwdYuPMxYD5gI/+dgAvc/5opW9JQ==", "dev": true, "requires": { "pretty-format": "^24.3.0" } }, "@types/testing-library__react": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.1.tgz", - "integrity": "sha512-8/toTJaIlS3BC7JrK2ElTnbjH8tmFP7atdL2ZsIa1JDmH9RKSm/7Wp5oMDJzXoWr988Mv7ym/XZ8LRglyoGCGw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", + "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", "dev": true, "requires": { "@types/react-dom": "*", @@ -3992,9 +3989,12 @@ "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, "chroma-js": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.0.6.tgz", - "integrity": "sha512-IiiClbBRkRwuXNl6impq5ssEhUGpmWvc5zzImZbDUWLWcFbj6ZbtsdZEx6sIXMKes7azgYaUpnmsY1T8BL6PqQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.1.0.tgz", + "integrity": "sha512-uiRdh4ZZy+UTPSrAdp8hqEdVb1EllLtTHOt5TMaOjJUvi+O54/83Fc5K2ld1P+TJX+dw5B+8/sCgzI6eaur/lg==", + "requires": { + "cross-env": "^6.0.3" + } }, "chrome-trace-event": { "version": "1.0.2", @@ -4560,6 +4560,52 @@ "gud": "^1.0.0" } }, + "cross-env": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz", + "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==", + "requires": { + "cross-spawn": "^7.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "cross-fetch": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.3.tgz", @@ -4943,9 +4989,9 @@ } }, "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz", + "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw==" }, "d3-axis": { "version": "1.0.12", @@ -5229,9 +5275,9 @@ } }, "date-fns": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.6.0.tgz", - "integrity": "sha512-F55YxqRdEfP/eYQmQjLN798v0AwLjmZ8nMBjdQvNwEE3N/zWVrlkkqT+9seBlPlsbkybG4JmWg3Ee3dIV9BcGQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.7.0.tgz", + "integrity": "sha512-wxYp2PGoUDN5ZEACc61aOtYFvSsJUylIvCjpjDOqM1UDaKIIuMJ9fAnMYFHV3TQaDpfTVxhwNK/GiCaHKuemTA==" }, "date-now": { "version": "0.1.4", @@ -5311,11 +5357,6 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, - "deepmerge": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.1.1.tgz", - "integrity": "sha512-+qO5WbNBKBaZez95TffdUDnGIo4+r5kmsX8aOb7PDHvXsTbghAmleuxjs6ytNaf5Eg4FGBXDS5vqO61TRi6BMg==" - }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -5541,18 +5582,18 @@ } }, "dom-helpers": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.2.tgz", - "integrity": "sha512-VrfjMjIzNgn2oB49wKl85fgs12ELjK0npu5Oryaiazyc6WuekO1go0E//0RJ8JvsBlfaAwq+IgX9M0XhwlEENA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz", + "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==", "requires": { "@babel/runtime": "^7.6.3", "csstype": "^2.6.7" }, "dependencies": { "@babel/runtime": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", - "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", "requires": { "regenerator-runtime": "^0.13.2" } @@ -5866,9 +5907,9 @@ } }, "enzyme-matchers": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-7.1.1.tgz", - "integrity": "sha512-fw/FxwpEg6n1KYpEHnhA44iFduYHDUVVePXSMmf883q/JDMXb+sIU55maSw2oFWqt9zd7rcGqmSV8sHQO5pReg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-7.1.2.tgz", + "integrity": "sha512-03WqAg2XDl7id9rARIO97HQ1JIw9F2heJ3R4meGu/13hx0ULTDEgl0E67MGl2Uq1jq1DyRnJfto1/VSzskdV5A==", "dev": true, "requires": { "circular-json-es6": "^2.0.1", @@ -5886,12 +5927,12 @@ } }, "enzyme-to-json": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.2.tgz", - "integrity": "sha512-tlzvJPPONTaTR2eKrWTt/pxknTjXgcNbxcYkxNfB0CwC8Pfc5xmSycaTwaQ1HXpN1zv6A7lAhnMV58HOIXTkFg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.4.3.tgz", + "integrity": "sha512-jqNEZlHqLdz7OTpXSzzghArSS3vigj67IU/fWkPyl1c0TCj9P5s6Ze0kRkYZWNEoCqCR79xlQbigYlMx5erh8A==", "dev": true, "requires": { - "lodash": "^4.17.12" + "lodash": "^4.17.15" } }, "errno": { @@ -6385,50 +6426,38 @@ } }, "eslint-plugin-jest": { - "version": "22.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.20.0.tgz", - "integrity": "sha512-UwHGXaYprxwd84Wer8H7jZS+5C3LeEaU8VD7NqORY6NmPJrs+9Ugbq3wyjqO3vWtSsDaLar2sqEB8COmOZA4zw==", + "version": "23.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.0.4.tgz", + "integrity": "sha512-OaP8hhT8chJNodUPvLJ6vl8gnalcsU/Ww1t9oR3HnGdEWjm/DdCCUXLOral+IPGAeWu/EwgVQCK/QtxALpH1Yw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "^1.13.0" + "@typescript-eslint/experimental-utils": "^2.5.0" }, "dependencies": { "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.7.0.tgz", + "integrity": "sha512-9/L/OJh2a5G2ltgBWJpHRfGnt61AgDeH6rsdg59BH0naQseSwR7abwHq3D5/op0KYD/zFT4LS5gGvWcMmegTEg==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" + "@typescript-eslint/typescript-estree": "2.7.0", + "eslint-scope": "^5.0.0" } }, "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.7.0.tgz", + "integrity": "sha512-vVCE/DY72N4RiJ/2f10PTyYekX2OLaltuSIBqeHYI44GQ940VCYioInIb8jKMrK9u855OEJdFC+HmWAZTnC+Ag==", "dev": true, "requires": { + "debug": "^4.1.1", + "glob": "^7.1.4", + "is-glob": "^4.0.1", "lodash.unescape": "4.0.1", - "semver": "5.5.0" - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "semver": "^6.3.0", + "tsutils": "^3.17.1" } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true } } }, @@ -6477,9 +6506,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.2.0.tgz", - "integrity": "sha512-jSlnBjV2cmyIeL555H/FbvuSbQ1AtpHjLMHuPrQnt1eVA6lX8yufdygh7AArI2m8ct7ChHGx2uOaCuxq2MUn6g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz", + "integrity": "sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw==", "dev": true }, "eslint-scope": { @@ -8541,14 +8570,6 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, - "is-plain-object": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", - "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", - "requires": { - "isobject": "^4.0.0" - } - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -8635,11 +8656,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" - }, "isomorphic-fetch": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", @@ -8821,9 +8837,9 @@ } }, "jest-environment-enzyme": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-7.1.1.tgz", - "integrity": "sha512-k+QJkK0iRtjWbNfKdtj1QQIs12JbbvPmHW30cSbDoIgOFO7Bd1lLo6qOabM+PdhPCeLWQ1D1ZoTrHPauXdYpzA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-7.1.2.tgz", + "integrity": "sha512-3tfaYAzO7qZSRrv+srQnfK16Vu5XwH/pHi8FpoqSHjKKngbHzXf7aBCBuWh8y3w0OtknHRfDMFrC60Khj+g1hA==", "dev": true, "requires": { "jest-environment-jsdom": "^24.0.0" @@ -8980,14 +8996,14 @@ } }, "jest-enzyme": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-7.1.1.tgz", - "integrity": "sha512-ujMi/2OF16rsjsS2ozdZCukfRZGC/Sb3MoJjINXITTvZM6lTL14lDliJr1kYIlUZVrphw0fmZkTNVTP7DnJ+Xw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-7.1.2.tgz", + "integrity": "sha512-j+jkph3t5hGBS12eOldpfsnERYRCHi4c/0KWPMnqRPoJJXvCpLIc5th1MHl0xDznQDXVU0AHUXg3rqMrf8vGpA==", "dev": true, "requires": { - "enzyme-matchers": "^7.1.1", + "enzyme-matchers": "^7.1.2", "enzyme-to-json": "^3.3.0", - "jest-environment-enzyme": "^7.1.1" + "jest-environment-enzyme": "^7.1.2" } }, "jest-get-type": { @@ -9798,9 +9814,9 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.0.tgz", - "integrity": "sha512-+hRyEfjRPFwTYMmSQ3/f7U9nP8ZNZmbkmUek760ZpxnCPWJIhaaLRuUSvpJ36fZKCGENxLwxClzwpOpnXNfChQ==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", + "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", "requires": { "abab": "^2.0.0", "acorn": "^7.1.0", @@ -9812,7 +9828,7 @@ "domexception": "^1.0.1", "escodegen": "^1.11.1", "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.1.4", + "nwsapi": "^2.2.0", "parse5": "5.1.0", "pn": "^1.1.0", "request": "^2.88.0", @@ -9836,9 +9852,9 @@ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==" }, "cssom": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.1.tgz", - "integrity": "sha512-6Aajq0XmukE7HdXUU6IoSWuH1H6gH9z6qmagsstTiN7cW2FNTsb+J2Chs+ufPgZCsV/yo8oaEudQLrb9dGxSVQ==" + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" }, "cssstyle": { "version": "2.0.0", @@ -9855,6 +9871,11 @@ } } }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, "tough-cookie": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", @@ -10126,11 +10147,6 @@ "immediate": "~3.0.5" } }, - "linear-layout-vector": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/linear-layout-vector/-/linear-layout-vector-0.0.1.tgz", - "integrity": "sha1-OYEU1zA7bsx/1rJzr3uEAdi6nHA=" - }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -10138,9 +10154,9 @@ "dev": true }, "lint-staged": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.2.tgz", - "integrity": "sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA==", + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.3.tgz", + "integrity": "sha512-PejnI+rwOAmKAIO+5UuAZU9gxdej/ovSEOAY34yMfC3OS4Ac82vCBPzAWLReR9zCPOMqeVwQRaZ3bUBpAsaL2Q==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -11332,6 +11348,12 @@ "run-queue": "^1.0.3" } }, + "mq-polyfill": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/mq-polyfill/-/mq-polyfill-1.1.8.tgz", + "integrity": "sha1-wUQZCyEhS/jYsJnn405soriI3BQ=", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -13364,9 +13386,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "6.8.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.3.tgz", - "integrity": "sha512-llcxWccnyaWlODe7A9hRjkvdCKamEKTh+wH8ITdTc3OhchaqUZteiSCX/2ablWHVrkVIe04dntnaZJ7BdyW0lQ==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.9.0.tgz", + "integrity": "sha512-KG4bhCFYapExLsUHrFt+kQVEegF2agm4cpF/VNc6pZVthIfCc/GK8t8VyNIE3nyXG9DK3Tf2EGkxjR6/uRdYsA==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -13453,9 +13475,9 @@ } }, "react": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz", - "integrity": "sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13573,20 +13595,20 @@ } }, "react-dom": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz", - "integrity": "sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", + "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.17.0" + "scheduler": "^0.18.0" }, "dependencies": { "scheduler": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz", - "integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -13666,9 +13688,9 @@ } }, "react-redux": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz", - "integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz", + "integrity": "sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w==", "requires": { "@babel/runtime": "^7.5.5", "hoist-non-react-statics": "^3.3.0", @@ -13881,15 +13903,15 @@ } }, "react-test-renderer": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.11.0.tgz", - "integrity": "sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.12.0.tgz", + "integrity": "sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", "react-is": "^16.8.6", - "scheduler": "^0.17.0" + "scheduler": "^0.18.0" } }, "react-transition-group": { @@ -13904,27 +13926,16 @@ } }, "react-virtualized": { - "version": "9.21.1", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.1.tgz", - "integrity": "sha512-E53vFjRRMCyUTEKuDLuGH1ld/9TFzjf/fFW816PE4HFXWZorESbSTYtiZz1oAjra0MminaUU1EnvUxoGuEFFPA==", + "version": "9.21.2", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.2.tgz", + "integrity": "sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA==", "requires": { "babel-runtime": "^6.26.0", "clsx": "^1.0.1", - "dom-helpers": "^2.4.0 || ^3.0.0", - "linear-layout-vector": "0.0.1", + "dom-helpers": "^5.0.0", "loose-envify": "^1.3.0", "prop-types": "^15.6.0", "react-lifecycles-compat": "^3.0.4" - }, - "dependencies": { - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "requires": { - "@babel/runtime": "^7.1.2" - } - } } }, "react-window": { @@ -14680,9 +14691,9 @@ } }, "scheduler": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz", - "integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -16396,9 +16407,9 @@ } }, "wait-for-expect": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.3.0.tgz", - "integrity": "sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz", + "integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==", "dev": true }, "walker": { diff --git a/package.json b/package.json index 22ef7b7..0bc691e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "entaxy", "description": "Your Personal Finance App", - "version": "0.1.0", + "version": "0.1.3", "private": true, "homepage": "https://entaxy.io", "scripts": { @@ -21,7 +21,7 @@ "debug": "echo 'chrome://inspect' && npx react-scripts --inspect-brk test --env=jsdom --runInBand --watch" }, "dependencies": { - "@material-ui/core": "^4.5.1", + "@material-ui/core": "^4.6.1", "@material-ui/icons": "^4.5.1", "@mdi/js": "^4.5.95", "@mdi/react": "^1.2.1", @@ -40,32 +40,32 @@ "@vx/tooltip": "0.0.192", "big.js": "^5.2.2", "blockstack": "^19.2.5", - "chroma-js": "^2.0.4", + "chroma-js": "^2.1.0", "classnames": "^2.2.6", "d3": "^5.12.0", - "d3-array": "^2.3.3", + "d3-array": "^2.4.0", "d3-sankey": "^0.12.3", - "date-fns": "^2.6.0", + "date-fns": "^2.7.0", "formik": "^1.5.8", - "jsdom": "^15.2.0", + "jsdom": "^15.2.1", "localforage": "^1.7.3", "lodash": "^4.17.15", "papaparse": "^5.1.0", "pluralize": "^8.0.0", "prop-types": "^15.7.2", - "query-string": "^6.8.3", - "react": "^16.11.0", + "query-string": "^6.9.0", + "react": "^16.12.0", "react-confirm": "^0.1.18", "react-date-range": "^1.0.0-beta", - "react-dom": "^16.11.0", + "react-dom": "^16.12.0", "react-dropzone": "^10.1.10", "react-motion": "^0.5.2", "react-number-format": "^4.3.1", - "react-redux": "^7.1.1", + "react-redux": "^7.1.3", "react-router-dom": "^5.1.2", "react-scripts": "^3.2.0", "react-select": "^3.0.8", - "react-virtualized": "^9.21.1", + "react-virtualized": "^9.21.2", "react-window": "^1.8.5", "recharts": "^1.8.5", "recompose": "^0.30.0", @@ -88,24 +88,25 @@ "not op_mini all" ], "devDependencies": { - "@testing-library/jest-dom": "^4.2.0", - "@testing-library/react": "^9.3.0", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", "coveralls": "^3.0.7", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.15.1", "eslint": "^6.6.0", "eslint-config-airbnb": "^18.0.1", "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.20.0", + "eslint-plugin-jest": "^23.0.4", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.2.0", + "eslint-plugin-react-hooks": "^2.3.0", "history": "^4.10.1", "http-proxy-middleware": "^0.20.0", "husky": "^3.0.9", - "jest-enzyme": "^7.1.1", - "lint-staged": "^9.4.2", - "react-test-renderer": "^16.11.0", + "jest-enzyme": "^7.1.2", + "lint-staged": "^9.4.3", + "mq-polyfill": "^1.1.8", + "react-test-renderer": "^16.12.0", "redux-mock-store": "^1.5.3" }, "husky": { diff --git a/src/common/ConfirmDialog/__test__/__snapshots__/index.test.jsx.snap b/src/common/ConfirmDialog/__test__/__snapshots__/index.test.jsx.snap index d47e140..4d24a68 100644 --- a/src/common/ConfirmDialog/__test__/__snapshots__/index.test.jsx.snap +++ b/src/common/ConfirmDialog/__test__/__snapshots__/index.test.jsx.snap @@ -5,10 +5,10 @@ exports[`ConfirmDialog matches snapshot 1`] = ` - + -
+
@@ -23,9 +23,9 @@ exports[`ConfirmDialog matches snapshot 1`] = `
- - -
+ + +
diff --git a/src/common/ConfirmDialog/index.jsx b/src/common/ConfirmDialog/index.jsx index 4f4e54e..0d9713b 100644 --- a/src/common/ConfirmDialog/index.jsx +++ b/src/common/ConfirmDialog/index.jsx @@ -38,7 +38,7 @@ ConfirmDialog.propTypes = { show: PropTypes.bool.isRequired, // from confirmable. indicates if the dialog is shown or not. proceed: PropTypes.func.isRequired, // from confirmable. call to close the dialog with promise resolved. dismiss: PropTypes.func.isRequired, // from confirmable. call to only close the dialog. - title: PropTypes.string.isRequired, // the title of the dialog + title: PropTypes.node.isRequired, // the title of the dialog description: PropTypes.string.isRequired // the description of the dialog } diff --git a/src/common/Header/__tests__/__snapshots__/index.test.jsx.snap b/src/common/Header/__tests__/__snapshots__/index.test.jsx.snap deleted file mode 100644 index f1fe089..0000000 --- a/src/common/Header/__tests__/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,118 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Header matches snapshot 1`] = ` -
-
-
-
- -
- - - Dashboard - - - -
-
- -
-
-
-
-
- content -
-
-`; diff --git a/src/common/Header/__tests__/index.test.jsx b/src/common/Header/__tests__/index.test.jsx index f61f177..386acc7 100644 --- a/src/common/Header/__tests__/index.test.jsx +++ b/src/common/Header/__tests__/index.test.jsx @@ -1,23 +1,69 @@ import React from 'react' -import renderer from 'react-test-renderer' -import { BrowserRouter } from 'react-router-dom' +import { render, fireEvent, waitForElement } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' import { Provider } from 'react-redux' -import { store } from '../../../store' +import { Router } from 'react-router-dom' +import { createMemoryHistory } from 'history' +import configureMockStore from 'redux-mock-store' import Header from '..' +import { initialState as userInitialState } from '../../../store/user/reducer' -jest.mock('../../LoginButton', () => 'LoginButton') +// https://github.com/mui-org/material-ui/issues/15726 +global.document.createRange = () => ({ + setStart: () => {}, + setEnd: () => {}, + commonAncestorContainer: { + nodeName: 'BODY', + ownerDocument: document + } +}) -describe('Header', () => { - it('matches snapshot', () => { - const component = renderer.create(( +const renderContent = () => { + const mockStore = configureMockStore() + const store = mockStore({ user: userInitialState }) + const history = createMemoryHistory() + + const props = { + match: { path: '/dashboard' } + } + return { + ...render( - -
+ +
content
- +
- )) - expect(component.toJSON()).toMatchSnapshot() + ), + props, + history + } +} + + +describe('Header', () => { + it('renders correctly', () => { + const { getByText } = renderContent() + + expect(getByText('Dashboard')).toBeInTheDocument() + expect(getByText('Budget')).toBeInTheDocument() + }) + + it('opens and closes menu', async () => { + const { getByText, queryByText, history } = renderContent() + const historyPushSpy = jest.spyOn(history, 'push') + + expect(queryByText('Money Flow')).not.toBeInTheDocument() + expect(queryByText('History')).not.toBeInTheDocument() + expect(queryByText('Categories')).not.toBeInTheDocument() + // open the budget menu + fireEvent.click(getByText('Budget')) + waitForElement(() => getByText('Money Flow')) + expect(getByText('History')).toBeInTheDocument() + expect(getByText('Categories')).toBeInTheDocument() + // close the menu + fireEvent.click(getByText('Categories')) + expect(historyPushSpy).toHaveBeenCalledWith('/budget-categories') }) }) diff --git a/src/common/Header/index.jsx b/src/common/Header/index.jsx index afdffdf..3dd8ddf 100644 --- a/src/common/Header/index.jsx +++ b/src/common/Header/index.jsx @@ -9,10 +9,17 @@ import Popper from '@material-ui/core/Popper' import MenuItem from '@material-ui/core/MenuItem' import MenuList from '@material-ui/core/MenuList' import ListItemText from '@material-ui/core/ListItemText' +import ListItemIcon from '@material-ui/core/ListItemIcon' import Grid from '@material-ui/core/Grid' import Paper from '@material-ui/core/Paper' import Fade from '@material-ui/core/Fade' import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown' +import Icon from '@mdi/react' +import { + mdiChartBellCurveCumulative, + mdiChartSnakeyVariant, + mdiTagMultiple +} from '@mdi/js' import Logo from '../Logo/index' import LoginButton from '../LoginButton' import LinkTo from '../LinkTo' @@ -68,51 +75,58 @@ const Header = ({ children, match }) => { - - <> - - - {anchorEl !== null && ( - - {({ TransitionProps }) => ( - - - - - - - - - - - - - - - - )} - + + + {anchorEl !== null && ( + + {({ TransitionProps }) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + )} - - + + )} diff --git a/src/common/LoginButton/__tests__/index.test.jsx b/src/common/LoginButton/__tests__/index.test.jsx index be802e1..9133854 100644 --- a/src/common/LoginButton/__tests__/index.test.jsx +++ b/src/common/LoginButton/__tests__/index.test.jsx @@ -1,22 +1,46 @@ import React from 'react' -import { render } from '@testing-library/react' +import { render, fireEvent, waitForElement } from '@testing-library/react' import '@testing-library/jest-dom/extend-expect' import { Provider } from 'react-redux' +import { Router } from 'react-router-dom' +import { createMemoryHistory } from 'history' import configureMockStore from 'redux-mock-store' import LoginButton from '..' +// https://github.com/mui-org/material-ui/issues/15726 +global.document.createRange = () => ({ + setStart: () => {}, + setEnd: () => {}, + commonAncestorContainer: { + nodeName: 'BODY', + ownerDocument: document + } +}) + +const mockStore = configureMockStore() + +const renderContent = (store) => { + const history = createMemoryHistory() + + return { + ...render( + + + + + + ), + history + } +} + describe('LoginButton', () => { it('renders nothing if user is not logged in', () => { const user = { isAuthenticatedWith: null } - const mockStore = configureMockStore() const store = mockStore({ user }) - const { container } = render( - - - - ) + const { container } = renderContent(store) expect(container).toBeEmpty() }) @@ -27,13 +51,8 @@ describe('LoginButton', () => { username: 'testuser', pictureUrl: 'http://someimage/' } - const mockStore = configureMockStore() const store = mockStore({ user }) - const { getByText } = render( - - - - ) + const { getByText } = renderContent(store) expect(getByText(user.name)).toBeInTheDocument() expect(getByText((_, elem) => elem.src === user.pictureUrl)).toBeInTheDocument() }) @@ -45,14 +64,33 @@ describe('LoginButton', () => { username: 'testuser', pictureUrl: 'http://someimage/' } - const mockStore = configureMockStore() const store = mockStore({ user }) - const { getByText, queryByText } = render( - - - - ) + const { getByText, queryByText } = renderContent(store) expect(getByText(user.name)).toBeInTheDocument() expect(queryByText((_, elem) => elem.src === user.pictureUrl)).toBeInTheDocument() }) + + it('opens and closes menu', async () => { + const user = { + isAuthenticatedWith: 'guest', + name: 'test user', + username: 'testuser', + pictureUrl: 'http://someimage/' + } + const store = mockStore({ user }) + const { getByText, queryByText, history } = renderContent(store) + const historyPushSpy = jest.spyOn(history, 'push') + + expect(queryByText(user.name)).toBeInTheDocument() + expect(queryByText('Settings')).not.toBeInTheDocument() + expect(queryByText('Close Session')).not.toBeInTheDocument() + // open the budget menu + fireEvent.click(getByText(user.name)) + waitForElement(() => getByText('Settings')) + expect(getByText('Settings')).toBeInTheDocument() + expect(getByText('Close session')).toBeInTheDocument() + // close the menu + fireEvent.click(getByText('Settings')) + expect(historyPushSpy).toHaveBeenCalledWith('/settings') + }) }) diff --git a/src/common/LoginButton/index.jsx b/src/common/LoginButton/index.jsx index 3de2d95..2f48a84 100644 --- a/src/common/LoginButton/index.jsx +++ b/src/common/LoginButton/index.jsx @@ -1,5 +1,5 @@ import React, { useState, useRef } from 'react' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import { makeStyles } from '@material-ui/core/styles' import Button from '@material-ui/core/Button' import Avatar from '@material-ui/core/Avatar' @@ -19,7 +19,6 @@ import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown' import LinkTo from '../LinkTo' import { userLogout } from '../../store' import packageJson from '../../../package.json' -import generateSeedData from '../../data/generateSeedData' const useStyles = makeStyles((theme) => ({ root: { @@ -39,7 +38,6 @@ const useStyles = makeStyles((theme) => ({ const LoginButton = () => { const classes = useStyles() - const dispatch = useDispatch() const user = useSelector((state) => state.user) const [open, setOpen] = useState(false) const anchorRef = useRef(null) @@ -54,10 +52,6 @@ const LoginButton = () => { } setOpen(false) } - const handleGererateSeedData = () => { - setOpen(false) - dispatch(generateSeedData()) - } if (!user.isAuthenticatedWith) return null return ( @@ -99,9 +93,6 @@ const LoginButton = () => { - - - Version  diff --git a/src/common/PopupDateRangePicker/index.jsx b/src/common/PopupDateRangePicker/index.jsx index a2843c5..3cd1d4b 100644 --- a/src/common/PopupDateRangePicker/index.jsx +++ b/src/common/PopupDateRangePicker/index.jsx @@ -38,6 +38,7 @@ const PopupDateRangePicker = ({ variant="contained" onClick={handleOpenCalendar} endIcon={} + data-testid="DateRangeButton" > {children} diff --git a/src/core/Accounts/Transactions/__tests__/__snapshots__/TransactionDialog.test.jsx.snap b/src/core/Accounts/Transactions/__tests__/__snapshots__/TransactionDialog.test.jsx.snap index bcb9825..10538eb 100644 --- a/src/core/Accounts/Transactions/__tests__/__snapshots__/TransactionDialog.test.jsx.snap +++ b/src/core/Accounts/Transactions/__tests__/__snapshots__/TransactionDialog.test.jsx.snap @@ -15,10 +15,10 @@ exports[`ConfirmDialog matches snapshot 1`] = ` - + -
+
@@ -33,9 +33,9 @@ exports[`ConfirmDialog matches snapshot 1`] = `
- - -
+ + +
@@ -92,26 +92,26 @@ exports[`ConfirmDialog matches snapshot 1`] = `
- - + +
- - - - -