diff --git a/package-lock.json b/package-lock.json index d26d98eea0d41766ed2bd1e8348799f014d34731..6f645d6359816e70a86080ba3db4c3cf143959bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -343,10 +343,13 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "asn1.js": { "version": "4.9.2", @@ -1732,11 +1735,10 @@ "dev": true }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, - "optional": true, "requires": { "tweetnacl": "0.14.5" } @@ -2378,9 +2380,9 @@ "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" }, "clean-css": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.9.tgz", - "integrity": "sha1-Nc7ornaHpJuYA09w3gDE7dOCYwE=", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", + "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", "dev": true, "requires": { "source-map": "0.5.7" @@ -3713,11 +3715,10 @@ "dev": true }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, - "optional": true, "requires": { "jsbn": "0.1.1" } @@ -4138,9 +4139,9 @@ } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "globals": { @@ -4150,9 +4151,9 @@ "dev": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "1.0.9", @@ -4421,9 +4422,9 @@ } }, "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { @@ -4802,9 +4803,9 @@ "dev": true }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { "is-number": "2.1.0", @@ -5006,9 +5007,9 @@ "dev": true }, "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", "dev": true, "optional": true, "requires": { @@ -5017,34 +5018,25 @@ }, "dependencies": { "abbrev": { - "version": "1.1.0", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { - "version": "1.1.1", + "version": "1.2.0", "bundled": true, "dev": true, "optional": true }, "are-we-there-yet": { - "version": "1.1.4", + "version": "1.1.5", "bundled": true, "dev": true, "optional": true, @@ -5053,36 +5045,6 @@ "readable-stream": "2.2.9" } }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, "balanced-match": { "version": "0.4.2", "bundled": true, @@ -5109,32 +5071,20 @@ "version": "2.10.1", "bundled": true, "dev": true, - "requires": { - "hoek": "2.x.x" - } + "optional": true }, "brace-expansion": { - "version": "1.1.7", + "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { - "balanced-match": "^0.4.1", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", + "chownr": { + "version": "1.1.1", "bundled": true, "dev": true, "optional": true @@ -5142,11 +5092,6 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, "dev": true, "requires": { "delayed-stream": "~1.0.0" @@ -5155,45 +5100,23 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, "dev": true, - "requires": { - "boom": "2.x.x" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } + "optional": true }, "debug": { - "version": "2.6.8", + "version": "2.6.9", "bundled": true, "dev": true, "optional": true, @@ -5202,13 +5125,7 @@ } }, "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", + "version": "0.6.0", "bundled": true, "dev": true }, @@ -5219,46 +5136,18 @@ "optional": true }, "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, - "extend": { - "version": "3.0.1", + "version": "1.0.3", "bundled": true, "dev": true, "optional": true }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", + "fs-minipass": { + "version": "1.2.5", "bundled": true, "dev": true, "optional": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.12" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -5304,27 +5193,11 @@ "wide-align": "1.1.2" } }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "glob": { - "version": "7.1.2", + "version": "7.1.3", "bundled": true, "dev": true, + "optional": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -5334,37 +5207,17 @@ "path-is-absolute": "1.0.1" } }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" - } - }, "has-unicode": { "version": "2.0.1", "bundled": true, "dev": true, "optional": true }, - "hawk": { - "version": "3.1.3", + "iconv-lite": { + "version": "0.4.24", "bundled": true, "dev": true, + "optional": true, "requires": { "boom": "2.10.1", "cryptiles": "2.0.5", @@ -5372,13 +5225,8 @@ "sntp": "1.0.9" } }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", + "ignore-walk": { + "version": "3.0.1", "bundled": true, "dev": true, "optional": true, @@ -5392,6 +5240,7 @@ "version": "1.0.6", "bundled": true, "dev": true, + "optional": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -5400,10 +5249,11 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { - "version": "1.3.4", + "version": "1.3.5", "bundled": true, "dev": true, "optional": true @@ -5412,68 +5262,76 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, - "is-typedarray": { + "isarray": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true }, - "isarray": { - "version": "1.0.0", + "minimatch": { + "version": "3.0.4", "bundled": true, - "dev": true + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } }, - "isstream": { - "version": "0.1.2", + "minimist": { + "version": "0.0.8", "bundled": true, "dev": true, "optional": true }, - "jodid25519": { - "version": "1.0.2", + "minipass": { + "version": "2.3.5", "bundled": true, "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", + "minizlib": { + "version": "1.2.1", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "minipass": "^2.2.1" + } }, - "json-stable-stringify": { - "version": "1.0.1", + "mkdirp": { + "version": "0.5.1", "bundled": true, "dev": true, "optional": true, "requires": { - "jsonify": "~0.0.0" + "minimist": "0.0.8" } }, - "json-stringify-safe": { - "version": "5.0.1", + "ms": { + "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, - "jsonify": { - "version": "0.0.0", + "needle": { + "version": "2.2.4", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } }, "jsprim": { "version": "1.4.0", @@ -5564,8 +5422,24 @@ "osenv": "^0.1.4" } }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npmlog": { - "version": "4.1.0", + "version": "4.1.2", "bundled": true, "dev": true, "optional": true, @@ -5581,12 +5455,6 @@ "bundled": true, "dev": true }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, "object-assign": { "version": "4.1.1", "bundled": true, @@ -5597,6 +5465,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1.0.2" } @@ -5614,7 +5483,7 @@ "optional": true }, "osenv": { - "version": "0.1.4", + "version": "0.1.5", "bundled": true, "dev": true, "optional": true, @@ -5626,33 +5495,17 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", + "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, "rc": { - "version": "1.2.1", + "version": "1.2.8", "bundled": true, "dev": true, "optional": true, @@ -5672,9 +5525,10 @@ } }, "readable-stream": { - "version": "2.2.9", + "version": "2.3.6", "bundled": true, "dev": true, + "optional": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -5685,8 +5539,8 @@ "util-deprecate": "1.0.2" } }, - "request": { - "version": "2.81.0", + "rimraf": { + "version": "2.6.3", "bundled": true, "dev": true, "optional": true, @@ -5715,21 +5569,28 @@ "uuid": "3.0.1" } }, - "rimraf": { - "version": "2.6.1", + "safe-buffer": { + "version": "5.1.2", "bundled": true, "dev": true, "requires": { "glob": "7.1.2" } }, - "safe-buffer": { - "version": "5.0.1", + "safer-buffer": { + "version": "2.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true }, "semver": { - "version": "5.3.0", + "version": "5.6.0", "bundled": true, "dev": true, "optional": true @@ -5783,6 +5644,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -5790,23 +5652,19 @@ } }, "string_decoder": { - "version": "1.0.1", + "version": "1.1.1", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "5.0.1" } }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, "strip-ansi": { "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "2.1.1" } @@ -5861,40 +5719,14 @@ "safe-buffer": "5.0.1" } }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, "dev": true, "optional": true }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, "wide-align": { - "version": "1.1.2", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true, @@ -5905,7 +5737,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true } } }, @@ -8025,9 +7864,9 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "1.0.9", @@ -8703,6 +8542,12 @@ "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=" }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", @@ -8921,9 +8766,9 @@ "dev": true }, "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.1.tgz", + "integrity": "sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==", "dev": true, "optional": true }, @@ -8993,6 +8838,12 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", "dev": true }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", @@ -10766,9 +10617,9 @@ } }, "original": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", - "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "dev": true, "requires": { "url-parse": "1.0.5" @@ -12613,23 +12464,23 @@ "dev": true }, "querystringify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", - "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, "raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", "requires": { "performance-now": "2.1.0" } }, "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", "dev": true, "requires": { "is-number": "3.0.0", @@ -14083,6 +13934,12 @@ "ret": "0.1.15" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "sane": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/sane/-/sane-1.6.0.tgz", @@ -14785,9 +14642,9 @@ "dev": true }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { "asn1": "0.2.3", @@ -15333,9 +15190,9 @@ } }, "test-exclude": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.1.1.tgz", - "integrity": "sha512-35+Asrsk3XHJDBgf/VRFexPgh3UyETv8IAn/LRTiZjVy6rjPVqdEk8dJcJYBzl1w0XCJM48lvTy8SfEsCWS4nA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", "dev": true, "requires": { "arrify": "1.0.1", @@ -15554,8 +15411,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true + "dev": true }, "type-check": { "version": "0.3.2", @@ -15980,9 +15836,9 @@ } }, "url-parse": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz", - "integrity": "sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", "dev": true, "requires": { "querystringify": "1.0.0", @@ -16118,9 +15974,9 @@ "dev": true }, "watchpack": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", - "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", "dev": true, "requires": { "async": "2.5.0", diff --git a/src/components/Blocnote/BudgetSimulator/BudgetChart.js b/src/components/Blocnote/BudgetSimulator/BudgetChart.js new file mode 100644 index 0000000000000000000000000000000000000000..516b8e71b11569735bc597c94ce2a511d660af92 --- /dev/null +++ b/src/components/Blocnote/BudgetSimulator/BudgetChart.js @@ -0,0 +1,91 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + LineChart, + Line, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + + +class BudgetChart extends Component { + constructor(props) { + super(props); + + this.state = { + successMessages: [], + error: false, + loading: false, + }; + } + + render() { + const data = []; + this.props.savingPotentialList.forEach((saving, index) => { + data.push({ + x_name: `${saving * 100}%`, + loan_only: this.props.budgets[0][index], + loan_first: this.props.budgets[1][index], + sf_first: this.props.budgets[2][index], + sf_max: this.props.budgets[3][index], + }); + }); + + const CustomizedLabel = React.createClass({ + render() { + const { x, y, stroke, value } = this.props; + + return ( + + {value} + + ); + }, + }); + + const renderLineChart = ( +
+ + + + + + + } /> + + + + + + +
+ ); + + return renderLineChart; + } +} + +BudgetChart.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + stroke: PropTypes.number, + value: PropTypes.number, + savingPotentialList: PropTypes.arrayOf, + budgets: PropTypes.arrayOf, + blockStyle: PropTypes.string, +}; + +export default BudgetChart; diff --git a/src/components/Blocnote/BudgetSimulator/BugetTable.js b/src/components/Blocnote/BudgetSimulator/BugetTable.js new file mode 100644 index 0000000000000000000000000000000000000000..85f575ee3e8ea6938fd21bdf1332df0dc07fdb45 --- /dev/null +++ b/src/components/Blocnote/BudgetSimulator/BugetTable.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; + + +class BudgetTable extends Component { + constructor(props) { + super(props); + + this.state = { + successMessages: [], + error: false, + loading: false, + }; + } + + render() { + const tableHeaderStyle = { + textAlign: 'center', + backgroundColor: '#EEEEEE', + color: '#000000', + fontWeight: 'bold', + }; + const header = [...['Savings'], ...this.props.savingPotentialList] + .map((columnName) => { + let cellValue = columnName; + if (typeof columnName !== 'string') { + cellValue *= 100; + cellValue = `${cellValue}%`; + } + return ( + + {cellValue} + + ); + }); + + let rows = []; + if (this.props.data !== null) { + rows = this.props.data.map((row) => { + const cells = row.map((cell) => { + let cellValue = cell; + if (typeof cellValue !== 'string') { + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + } + + return ( + + {cellValue} + + ); + }); + return ( + + {cells} + + ); + }); + } + + return ( +
+ + + + + + + {header} + + + + {rows} + +
+ {this.props.tableName} +
+
+ ); + } +} + +BudgetTable.propTypes = { + tableName: PropTypes.string, + savingPotentialList: PropTypes.arrayOf, + data: PropTypes.arrayOf, +}; + +export default BudgetTable; diff --git a/src/components/Blocnote/FinancialInputs/Bills.js b/src/components/Blocnote/FinancialInputs/Bills.js new file mode 100644 index 0000000000000000000000000000000000000000..e37b1d6a64dd71c6b6f0b1461172af17f762f6cd --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/Bills.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Row, +} from 'reactstrap'; +import BillsTable from './BillsTable'; + + +const Bills = (props) => { + const BillsTables = []; + const billsExist = { + gas: false, + oil: false, + water: false, + electric: false, + }; + + if (props.data !== null) { + console.log(props.data); // eslint-disable-line + Object.keys(props.data).forEach(billName => { + BillsTables.push( +
+ +
+ ); + billsExist[billName] = true; + }); + } + + Object.entries(billsExist).forEach(billExist => { + if (billExist[1] === false) { + BillsTables.push( +
+ +
+ ); + } + }); + + return ( +
+

+ Energy Bills +

+ + {BillsTables} + +
+ ); +}; + +Bills.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + data: PropTypes.objectOf, +}; + +export default Bills; diff --git a/src/components/Blocnote/FinancialInputs/BillsOverview.js b/src/components/Blocnote/FinancialInputs/BillsOverview.js new file mode 100644 index 0000000000000000000000000000000000000000..1fa5a241447d45ee2e4b46e74ee22d213c9c226b --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/BillsOverview.js @@ -0,0 +1,283 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, + Button, + Table, +} from 'reactstrap'; +import ResultMessage from './ResultMessage'; + + +class BillsOverview extends Component { + constructor(props) { + super(props); + this.toggleEstimation = this.toggleEstimation.bind(this); + this.changeEstimation = this.changeEstimation.bind(this); + const data = {}; + if (Object.keys(this.props.data).length !== 0) { + ['electric', 'water', 'gas', 'oil'].forEach((billName) => { + Object.entries(this.props.data[billName]).forEach((bill) => { + const keyName = `${billName}-value-${bill[0]}`; + data[keyName] = bill[1]; + }); + }); + } + + this.state = { + estimationDropdownOpen: false, + estimationDropDownValue: 'Select Estimation', + estimationOptions: [ + { id: 'RoughEstimation', key: 'RoughEstimation', name: 'Rough Estimation' }, + { id: 'Fancy Estimation', key: 'FancyEstimation', name: 'Fancy Estimation' }, + ], + action: null, + postBills: data, + }; + } + + toggleEstimation() { + this.setState({ + estimationDropdownOpen: !this.state.estimationDropdownOpen, + }); + } + + changeEstimation(e) { + this.setState({ estimationDropDownValue: e.currentTarget.textContent }); + } + + handleCreateBillsOverview = () => { + this.props.createBillsOverview( + this.props.buildingId, + this.state.postBills, + ); + this.setState({ action: 'created' }); + } + + handleUpdateBillsOverview = () => { + const data = {}; + data['Estimation Model'] = this.state.estimationDropDownValue; + + if (data['Estimation Model'] === 'Select Estimation') { + alert('Please select an estimation model'); + } else { + this.props.updateBillsOverview( + this.props.buildingId, + data, + ); + this.setState({ action: 'updated' }); + } + } + + buildHeader = (headerNames) => { + return [...headerNames, ...Object.keys(this.props.data.electric)].map((name, key) => { + return ( + + {name} + + ); + }); + } + + buildFooter = (footerNames) => { + const cellValues = Object.values(this.props.data.total_annual_charge).map((amount) => { + let cellValue = amount; + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + return `$${cellValue}`; + }); + return [...footerNames, ...cellValues].map((name, key) => { + return ( + + {name} + + ); + }); + } + + render() { + const validBillNames = ['electric', 'water', 'gas', 'oil']; + const estimationOptions = this.state.estimationOptions.map(e => { + return ( + + {e.name} + + ); + }); + let header = []; + let footer = []; + let rows = []; + let saveButton = ''; + + if (Object.keys(this.props.data).length !== 0) { + const headerNames = ['Data Source', 'Utility']; + const footerNames = ['', 'Total Annual Charge']; + header = this.buildHeader(headerNames); + footer = this.buildFooter(footerNames); + rows = Object.keys(this.props.data) + .filter(billName => validBillNames.includes(billName)) + .map((billName, trKey) => { + const cells = [ + ...['Annual Estimate', billName.charAt(0).toUpperCase() + billName.slice(1)], + ...Object.values(this.props.data[billName]), + ]; + const cellsData = Object.values(cells).map((amount, tdKey) => { + let cellValue = amount; + + if (!isNaN(cellValue)) { + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + } + + return ( + + + {cellValue} + + + ); + }); + + return ( + + {cellsData} + + ); + }); + + saveButton = ( +
+
+ +
+
+ ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Processing ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.data !== null + && this.state.action !== null) { + messageContent = this.state.action.charAt(0).toUpperCase() + this.state.action.slice(1); + messageStyle = this.props.successMessageStyle; + } + + return ( +
+

+ Energy Bills Overview +

+
+
+ + + {this.state.estimationDropDownValue} + + + {estimationOptions} + + +
+
+ +
+
+ +
+
+
+
+ + + {header} + + + {rows} + {footer} + +
+
+
+ {saveButton} +
+ ); + } +} + +BillsOverview.propTypes = { + data: PropTypes.shape({ + electric: PropTypes.objectOf, + electric_user: PropTypes.string, + gas: PropTypes.objectOf, + gas_user: PropTypes.string, + oil: PropTypes.objectOf, + oil_user: PropTypes.string, + water: PropTypes.objectOf, + water_user: PropTypes.string, + total_annual_charge: PropTypes.objectOf, + }), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + paddingLeft: PropTypes.string, + marginBottom: PropTypes.string, + }), + buildingId: PropTypes.number, + createBillsOverview: PropTypes.func, + updateBillsOverview: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +export default BillsOverview; diff --git a/src/components/Blocnote/FinancialInputs/BillsRow.js b/src/components/Blocnote/FinancialInputs/BillsRow.js new file mode 100644 index 0000000000000000000000000000000000000000..d0a59b29e8a5c58a2b29b8bad9063f31f0613cbb --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/BillsRow.js @@ -0,0 +1,31 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +const BillsRow = (props) => { + return ( + + + {props.dateFrom} + + + {props.dateTo} + + + {props.usage} + + + $ {props.charge} + + + ); +}; + +BillsRow.propTypes = { + dateFrom: PropTypes.number, + dateTo: PropTypes.number, + usage: PropTypes.func, + charge: PropTypes.func, +}; + +export default BillsRow; diff --git a/src/components/Blocnote/FinancialInputs/BillsSummary.js b/src/components/Blocnote/FinancialInputs/BillsSummary.js new file mode 100644 index 0000000000000000000000000000000000000000..05b6b1ba4b27932cc471cb0f2937ae149cc85e0c --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/BillsSummary.js @@ -0,0 +1,115 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Row, +} from 'reactstrap'; +import BillsSummaryTable from './BillsSummaryTable'; + + +class BillsSummary extends Component { + constructor(props) { + super(props); + this.state = { + billsSummary: props.data, + }; + } + + render() { + const BillsSummaryTables = []; + + if (this.state.billsSummary !== null) { + console.log(this.state.billsSummary); // eslint-disable-line + Object.keys(this.state.billsSummary).forEach(billName => { + BillsSummaryTables.push( +
+ +
+ ); + }); + } + + return ( +
+

+ Bills Summary +

+ + {BillsSummaryTables} + +
+ ); + } +} + +BillsSummary.propTypes = { + data: PropTypes.shape({ + electric: PropTypes.arrayOf( + PropTypes.shape({ + year: PropTypes.string, + charge: PropTypes.string, + id: PropTypes.number, + }) + ), + gas: PropTypes.arrayOf( + PropTypes.shape({ + year: PropTypes.string, + charge: PropTypes.string, + id: PropTypes.number, + }) + ), + oil: PropTypes.arrayOf( + PropTypes.shape({ + year: PropTypes.string, + charge: PropTypes.string, + id: PropTypes.number, + }) + ), + water: PropTypes.arrayOf( + PropTypes.shape({ + year: PropTypes.string, + charge: PropTypes.string, + id: PropTypes.number, + }) + ), + }), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + paddingLeft: PropTypes.string, + marginBottom: PropTypes.string, + }), + buildingId: PropTypes.number, + createBillsSummary: PropTypes.func, + updateBillsSummary: PropTypes.func, + deleteBillsSummary: PropTypes.func, +}; + +BillsSummary.defaultProps = { + data: null, +}; + +export default BillsSummary; diff --git a/src/components/Blocnote/FinancialInputs/BillsSummaryRow.js b/src/components/Blocnote/FinancialInputs/BillsSummaryRow.js new file mode 100644 index 0000000000000000000000000000000000000000..b7479d8d08cdc67d2e2549de1d34c484f9d587c7 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/BillsSummaryRow.js @@ -0,0 +1,98 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + InputGroup, + InputGroupAddon, + Input, +} from 'reactstrap'; + + +class BillsSummaryRow extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(this); + this.handleUpdateBillsSummary = this.handleUpdateBillsSummary.bind(this); + this.handleDeleteBillsSummary = this.handleDeleteBillsSummary.bind(this); + this.state = { + id: props.id, + year: props.year, + charge: props.charge, + }; + } + + handleUpdateBillsSummary() { + console.log(this.state); // eslint-disable-line + this.props.updateBillsSummary(this.state); + } + + handleDeleteBillsSummary() { + this.props.deleteBillsSummary(this.props.id); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }); + } + + render() { + return ( + + + + + Year + + + + + + + + $ + + + + + + {' '} + + + + ); + } +} + +BillsSummaryRow.propTypes = { + id: PropTypes.number, + year: PropTypes.number, + charge: PropTypes.number, + updateBillsSummary: PropTypes.func, + deleteBillsSummary: PropTypes.func, +}; + +export default BillsSummaryRow; diff --git a/src/components/Blocnote/FinancialInputs/BillsSummaryTable.js b/src/components/Blocnote/FinancialInputs/BillsSummaryTable.js new file mode 100644 index 0000000000000000000000000000000000000000..a165391d5cae2459566eae7084d31ac7789754be --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/BillsSummaryTable.js @@ -0,0 +1,233 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Table, + Button, +} from 'reactstrap'; +import BillsSummaryRow from './BillsSummaryRow'; +import ResultMessage from './../../../components/Blocnote/FinancialInputs/ResultMessage'; + + +class BillsSummaryTable extends Component { + constructor(props) { + super(props); + this.handleCreateBillsSummary = this.handleCreateBillsSummary.bind(this); + this.state = { + billName: props.billName, + billData: props.billData, + year: '', + charge: '', + action: null, + }; + } + + handleCreateBillsSummary = () => { + this.props.createBillsSummary( + this.props.buildingId, + { + utility_type: this.props.billName, + year: this.state.year, + charge: this.state.charge, + }, () => { + console.log('saga done!'); // eslint-disable-line + } + ); + const newBill = { + id: 100, + utility_type: this.props.billName, + year: this.state.year, + charge: this.state.charge, + }; + this.setState({ billData: [...this.state.billData, newBill] }, () => { + console.log(this.state.billData, 'bill added!'); // eslint-disable-line + this.setState({ + year: '', + charge: '', + action: 'created', + }); + }); + } + + updateBillsSummary = (billData) => { + this.props.updateBillsSummary( + this.props.buildingId, + billData, + ); + this.setState({ action: 'updated' }); + } + + deleteBillsSummary = id => { + if (confirm('Are you sure to delete this bill?') === true) { + this.props.deleteBillsSummary( + this.props.buildingId, + { id }, + ); + const billDataCopy = this.state.billData.filter(bill => bill.id !== id); + this.setState({ + billData: billDataCopy, + action: 'deleted', + }); + } + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }); + } + + addRow = () => { + const newBillData = { + id: this.state.id, + year: this.state.year, + charge: this.state.charge, + }; + const billDataCopy = this.state.billData; + billDataCopy.push(newBillData); + this.setState({ billData: billDataCopy }, () => { + console.log(this.state.billData, 'New bill added!'); // eslint-disable-line + this.setState({ + id: this.state.id + 1, + year: '', + charge: '', + }); + }); + } + + render() { + let rows = []; + + if (this.state.billData.length !== 0) { + rows = this.state.billData.map((bill) => { + // charge = charge.toLocaleString(); + return ( + + ); + }); + } else { + rows = ( + + + Currently no bill. + + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Processing ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.billData !== null + && this.state.action !== null) { + messageContent = this.state.action.charAt(0).toUpperCase() + this.state.action.slice(1); + messageStyle = this.props.successMessageStyle; + } + + const billName = this.props.billName.charAt(0).toUpperCase() + this.props.billName.slice(1); + return ( +
+
+
+ {billName} Annual Charge +
+
+ {' '}{' '} + +
+
+ + + {rows} + +
+
+ ); + } +} + +BillsSummaryTable.propTypes = { + buildingId: PropTypes.number, + billName: PropTypes.string, + billData: PropTypes.arrayOf( + PropTypes.shape({ + year: PropTypes.string, + charge: PropTypes.string, + id: PropTypes.number, + }) + ), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + createBillsSummary: PropTypes.func, + updateBillsSummary: PropTypes.func, + deleteBillsSummary: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +BillsSummaryTable.defaultProps = { + billData: [], +}; + +export default BillsSummaryTable; diff --git a/src/components/Blocnote/FinancialInputs/BillsTable.js b/src/components/Blocnote/FinancialInputs/BillsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..1a2f32a48ae1e855e6f919e87431b1d37e7dde6d --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/BillsTable.js @@ -0,0 +1,198 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Table, + Button, +} from 'reactstrap'; +import BillsRow from './BillsRow'; + + +class BillsTable extends Component { + + constructor(props) { + global.expandNum = 15; + super(props); + this.state = { + billsData: (this.props.billsData.length > global.expandNum) ? + this.props.billsData.slice(0, global.expandNum) : this.props.billsData, + showExpandButton: (this.props.billsData.length > global.expandNum) === true, + showCollapseButton: false, + }; + } + + expandBills = () => { + console.log(this.props.billsData.slice(0, this.state.billsData.length + global.expandNum)); // eslint-disable-line + this.setState({ + billsData: this.props.billsData.slice(0, this.state.billsData.length + global.expandNum), + }, () => { + console.log('Expanded'); // eslint-disable-line + this.setState({ + showCollapseButton: true, + }); + if (this.state.billsData.length === this.props.billsData.length) { + console.log(this.props.billsData.length); // eslint-disable-line + this.setState({ + showExpandButton: false, + }); + } + }); + } + + collapeBills = () => { + const collapeNum = this.state.billsData.length === this.props.billsData.length ? + this.state.billsData.length % global.expandNum : global.expandNum; + this.setState({ + billsData: this.props.billsData.slice(0, this.state.billsData.length - collapeNum), + }, () => { + console.log('Collapsed'); // eslint-disable-line + this.setState({ + showExpandButton: true, + }); + if (this.state.billsData.length <= global.expandNum) { + this.setState({ + showCollapseButton: false, + }); + } + }); + } + + render() { + const headerStyle = { + textAlign: 'center', + fontWeight: 'bold', + // marginBottom: '15px', + padding: '15px', + borderBottom: '1px solid #999999', + background: '#EEEEEE', + }; + const tdStyle = { + textAlign: 'center', + fontSize: '13px', + }; + const buttonStyle = { + textAlign: 'center', + marginBottom: '30px', + marginRight: '-10px', + cursor: 'pointer', + }; + + const billsName = this.props.billsName.charAt(0).toUpperCase() + this.props.billsName.slice(1); + let header = ( + + Date From + Date To + Usage (kWh) + Amount + + ); + let rows = []; + let expandButton = null; + let collapseButton = null; + const showBillNum = this.props.billsData.length > 0 ? + `Showing ${this.state.billsData.length} of ${this.props.billsData.length}` : null; + + if (this.state.billsData.length !== 0) { + rows = this.state.billsData.map((bill) => { + // charge = charge.toLocaleString(); + return ( + + ); + }); + } else { + header = ''; + rows = ( + + + There is no bill. + + + ); + } + + if (this.state.showExpandButton) { + expandButton = ( + + ); + } + if (this.state.showCollapseButton) { + collapseButton = ( + + ); + } + + return ( +
+ + + + + + {header} + + {rows} + +
+ {billsName} Bills + + {showBillNum} +
+
+ {expandButton} + {collapseButton} +
+
+ ); + } +} + +BillsTable.propTypes = { + billsName: PropTypes.string, + billsData: PropTypes.arrayOf( + PropTypes.shape({ + year: PropTypes.string, + charge: PropTypes.string, + id: PropTypes.number, + }) + ), + // deleteRow: PropTypes.func, +}; + +BillsTable.defaultProps = { + billsData: [], +}; + +export default BillsTable; diff --git a/src/components/Blocnote/FinancialInputs/CashBalance.js b/src/components/Blocnote/FinancialInputs/CashBalance.js new file mode 100644 index 0000000000000000000000000000000000000000..2434ee26ee983d862725b63139c1a221ef1ec619 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/CashBalance.js @@ -0,0 +1,254 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + Table, +} from 'reactstrap'; +import CashBalanceRow from './CashBalanceRow'; +import ResultMessage from './ResultMessage'; + + +class CashBalance extends Component { + constructor(props) { + super(props); + this.deleteRow = this.deleteRow.bind(this); + this.addRow = this.addRow.bind(this); + this.state = { + cashBalance: this.props.data, + id: this.props.data.length, + balance_amount: null, + statement_date: null, + is_from_balance_sheet: false, + action: null, + }; + } + + validateInputs = () => { + const emptyFields = []; + if (this.state.cashBalance.length > 0) { + Object.values(this.state.cashBalance).forEach(statement => { + if (statement.balance_amount === null && + !emptyFields.includes('Balance Amount')) { + emptyFields.push('Balance Amount'); + } + if (statement.statement_date === null && + !emptyFields.includes('Statement Date')) { + emptyFields.push('Statement Date'); + } + }); + } + + if (emptyFields.length > 0) { + alert(`Please fill in ${emptyFields.join(', ')} fields`); + return false; + } + return true; + } + + deleteRow = id => { + if (this.state.cashBalance.length === 1) { + alert('Sorry, you need at least one statement!'); + } else if (confirm('Are you sure to delete this statement?') === true) { + const cashBalanceCopy = this.state.cashBalance.filter((statement, index) => { + console.log(statement); // eslint-disable-line + return index !== id; + }); + this.setState({ cashBalance: cashBalanceCopy }, () => { + console.log('Statement deleted!'); // eslint-disable-line + }); + } + } + + addRow = () => { + if (this.validateInputs() === true) { + const newStatement = { + id: this.state.id, + balance_amount: this.state.balance_amount, + statement_date: this.state.statement_date, + is_from_balance_dheet: this.state.is_from_balance_dheet, + }; + const cashBalanceCopy = this.state.cashBalance; + cashBalanceCopy.push(newStatement); + this.setState({ cashBalance: cashBalanceCopy }, () => { + console.log(this.state.cashBalance, 'New statement added!'); // eslint-disable-line + this.setState({ + id: this.state.id + 1, + balance_amount: null, + statement_date: null, + is_fromBalance_sheet: false, + }); + }); + } + } + + updateRow = (row) => { + const cashBalance = this.state.cashBalance.map((statement, id) => { + if (id === row.id) { + const tmp = { id }; + tmp.balance_amount = row.balance_amount; + tmp.statement_date = row.statement_date; + tmp.is_from_balance_sheet = row.is_from_balance_sheet; + return tmp; + } + return statement; + }); + this.setState({ cashBalance }, () => { + console.log(cashBalance); // eslint-disable-line + }); + } + + handleUpdateCashBalance = () => { + if (this.validateInputs() === true) { + this.props.updateCashBalance( + this.props.buildingId, + this.state.cashBalance, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + } + + render() { + const header = [ + '#Statement', + 'Balance Amount', + 'Statement Date', + 'Balance Sheet', + 'Option'].map((title, key) => { + return ( + + {title} + + ); + }); + + let saveButton = null; + let rows = []; + if (Object.keys(this.state.cashBalance).length !== 0) { + rows = this.state.cashBalance.map((statement, index) => { + return ( + + ); + }); + saveButton = ( + + ); + } else { + rows = ( + + + Currently no statements. + + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Updating ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.data !== null + && this.state.action) { + messageContent = 'Saved!'; + messageStyle = this.props.successMessageStyle; + } + + return ( +
+

+ Cash Balance +

+
+
+ + + {header} + + + {rows} + +
+
+
+
+
+    +   + {saveButton} +
+
+
+ ); + } +} + +CashBalance.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + data: PropTypes.arrayOf( + PropTypes.shape({ + balance_amount: PropTypes.string, + is_from_balance_sheet: PropTypes.boolean, + statement_date: PropTypes.string, + }), + ), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + buildingId: PropTypes.number, + updateCashBalance: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +export default CashBalance; diff --git a/src/components/Blocnote/FinancialInputs/CashBalanceRow.js b/src/components/Blocnote/FinancialInputs/CashBalanceRow.js new file mode 100644 index 0000000000000000000000000000000000000000..332bbfc68682f06ef0159946ae2190d32d0f728c --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/CashBalanceRow.js @@ -0,0 +1,99 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + InputGroup, + InputGroupAddon, + Input, + Button, +} from 'reactstrap'; + + +class CashBalanceRow extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(this); + this.onDelEvent = this.onDelEvent.bind(this); + this.state = { + id: props.id, + balance_amount: props.balanceAmount, + statement_date: props.statementDate, + is_from_balance_sheet: props.isFromBalanceSheet, + }; + } + + onDelEvent() { + this.props.onDelEvent(this.props.id); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: + event.target.name === 'is_from_balance_sheet' ? event.target.checked : event.target.value }, + () => { + this.props.onChangeEvent(this.state); + }); + } + + render() { + return ( + + + {this.props.id + 1} + + + + + $ + + + + + + + + + + + + + + + ); + } +} + +CashBalanceRow.propTypes = { + id: PropTypes.number, + balanceAmount: PropTypes.number, + statementDate: PropTypes.string, + isFromBalanceSheet: PropTypes.bool, + onDelEvent: PropTypes.func, + onChangeEvent: PropTypes.func, +}; + +export default CashBalanceRow; diff --git a/src/components/Blocnote/FinancialInputs/CustomerPreference.js b/src/components/Blocnote/FinancialInputs/CustomerPreference.js new file mode 100644 index 0000000000000000000000000000000000000000..5741596caa05d82192d025ecf563917fda2b6b9d --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/CustomerPreference.js @@ -0,0 +1,213 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + Table, + Input, + InputGroup, + InputGroupAddon, +} from 'reactstrap'; +import ResultMessage from './ResultMessage'; + + +class CustomerPreference extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(this); + this.state = { + downpayment: this.props.data.downpayment, + expectedNetNoiDscr: this.props.data.expected_net_noi_dscr, + expectedPayback: this.props.data.expected_payback, + updated: false, + }; + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }); + } + + handleUpdateCustomerPreference = () => { + this.props.updateCustomerPreference( + this.props.buildingId, + { + downpayment: parseFloat(this.state.downpayment).toFixed(2), + expected_net_noi_dscr: parseInt(this.state.expectedNetNoiDscr, 10), + expected_payback: this.state.expectedPayback, + }, + ); + this.setState({ updated: true }); + } + + render() { + const header = [ + 'Preference', + 'Value (if values is not known, please leave it blank)', + ].map((title, key) => { + return ( + + {title} + + ); + }); + + const rows = []; + const style = { fontWeight: 'bold' }; + if (this.props.data !== null) { + rows.push( + + + Affordable Downpayment (?) + + + + + $ + + + + + + ); + rows.push( + + + Expected Payback (?) + + + + + + Months + + + + + ); + rows.push( + + + Expected Saving DSCR (?) + + + + + + X + + + + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Updating ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.data !== null + && this.state.updated) { + messageContent = 'Saved!'; + messageStyle = this.props.successMessageStyle; + } + + return ( +
+

+ Customer Preference +

+
+
+ + + {header} + + + {rows} + +
+
+
+
+
+    + +
+
+
+ ); + } +} + +CustomerPreference.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + data: PropTypes.shape({ + downpayment: PropTypes.string, + expected_net_noi_dscr: PropTypes.string, + expected_payback: PropTypes.string, + }), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + buildingId: PropTypes.number, + updateCustomerPreference: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +export default CustomerPreference; diff --git a/src/components/Blocnote/FinancialInputs/FinanceOverview.js b/src/components/Blocnote/FinancialInputs/FinanceOverview.js new file mode 100644 index 0000000000000000000000000000000000000000..8e25a112ad651cd55431a1ded48ffad849627157 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/FinanceOverview.js @@ -0,0 +1,406 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, + Button, + Form, + FormGroup, + Input, + InputGroup, + InputGroupAddon, + Label, +} from 'reactstrap'; +// import CSRFToken from './csrftoken'; +import ResultMessage from './ResultMessage'; + + +class FinanceOverview extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(this); + this.toggleFund = this.toggleFund.bind(this); + this.changeFund = this.changeFund.bind(this); + this.submitForm = this.submitForm.bind(this); + this.state = { + fundOptions: props.fundOptions, + fundDropDownId: props.fundDropDownId, + fundDropDownValue: props.fundDropDownValue, + fundDropdownOpen: false, + didFundChange: false, + isFirstTime: false, + csrftoken: null, + showLoanOptionsTable: false, + messageStyle: {}, + messageContent: null, + proFormaStartDate: props.proFormaStartDate, + proFormaDuration: props.proFormaDuration, + analysisDate: props.analysisDate, + anticipatedConstructionStartDate: props.anticipatedConstructionStartDate, + anticipatedCommissioningDate: props.anticipatedCommissioningDate, + anticipatedConstructionPeriod: props.anticipatedConstructionPeriod, + }; + } + + toggleFund = () => { + this.setState({ + fundDropdownOpen: !this.state.fundDropdownOpen, + }); + } + + changeFund = (event) => { + this.setState({ fundDropDownValue: event.currentTarget.textContent }); + this.setState({ fundDropDownId: event.currentTarget.id }); + } + + submitForm = (event) => { + event.preventDefault(); + const formData = new FormData(event.target); + formData.set('fund_id', this.state.fundDropDownId); + + // this.setState({ + // csrftoken: formData.get('csrfmiddlewaretoken'), + // }); + + if (formData.get('fund_id') === '0') { + this.setState({ + messageContent: 'Please select a fund type', + messageStyle: this.props.errorMessageStyle, + }); + } else { + fetch(`${this.props.baseURL}/loan-options/?loans=all-loans`) + .then((res) => { + this.setState({ + isFirstTime: !res.load, + }); + const result = {}; + formData.forEach((value, field) => { + const key = field.split(/(?=[A-Z])/).join('_').toLowerCase(); + result[key] = value; + }); + const constructionStartDate = new Date(result.anticipated_construction_start_date); + const constructionCommissioningDate = new Date(result.anticipated_commissioning_date); + if (constructionStartDate > constructionCommissioningDate) { + this.setState({ + messageContent: 'Anticipated Commissioning date has to be after Anticipated Construction start date', + messageStyle: this.props.errorMessageStyle, + }); + } else { + this.loadFinancialOverview(result); + } + }); + } + } + + loadFinancialOverview = (result) => { + fetch(`${this.props.baseURL}/finance-overview/`, { + method: 'PUT', + credentials: 'same-origin', + body: JSON.stringify(result), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': this.state.csrftoken, + }), + }).then((res) => { + if (!res.err) { + this.setState({ + messageContent: 'Saved.', + messageStyle: this.props.successMessageStyle, + }); + + if (this.state.didFundChange) { + this.setState({ + messageContent: `${this.state.responseMessage} Loan Options table is reloaded.`, + messageStyle: this.props.errorMessageStyle, + }); + } + + if (this.state.didFundChange || this.state.isFirstTime) { + this.setState({ + didFundChange: false, + }); + this.deleteLoanOptions(); + } + } else { + const errors = []; + res.err.responseBody.then((error) => { + Object.keys(error).forEach((key) => { + errors.push(`${error[key][0]}`); + }); + }); + this.setState({ + errorMessage: errors, + }); + } + }); + } + + deleteLoanOptions = () => { + fetch(`${this.props.baseURL}/loan-options/?loans=delete-loan-options`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + }).then(() => { + this.setState({ + showLoanOptionsTable: false, + }); + }); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }); + } + + render() { + const labelStyle = { + fontSize: '13px', + textAlign: 'left', + fontWeight: 'bold', + paddingLeft: '15px', + }; + let mainContent = ''; + + if (this.props.fundOptions !== null) { + const fundOptions = []; + this.props.fundOptions.forEach((fund) => { + fundOptions.push( + + {fund.fund_name} + + ); + }); + + mainContent = ( +
+ {/* */} +

+ Financial Overview +

+
+
+ + + + + + + + + + Years + + + +
+
+ + + + + + + + + {this.state.fundDropDownValue} + + + {fundOptions} + + + +
+
+ + + + + + + + +
+
+ + + + + + Weeks + + + + + + + + + + +
+
+ + ); + } + + return ( +
+ {mainContent} +
+ ); + } +} + +FinanceOverview.propTypes = { + fundDropDownId: PropTypes.number, + fundDropDownValue: PropTypes.string, + fundOptions: PropTypes.arrayOf( + PropTypes.shape({ + fund_id: PropTypes.number, + fund_name: PropTypes.string, + }) + ), + proFormaStartDate: PropTypes.string, + proFormaDuration: PropTypes.string, + analysisDate: PropTypes.string, + anticipatedConstructionStartDate: PropTypes.string, + anticipatedCommissioningDate: PropTypes.string, + anticipatedConstructionPeriod: PropTypes.number, + baseURL: PropTypes.string, + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + paddingLeft: PropTypes.string, + marginBottom: PropTypes.string, + }), +}; + +FinanceOverview.defaultProps = { + analysis_date: '', + anticipated_commissioning_date: '', + anticipated_construction_period: '', + anticipated_construction_start_date: '', + fund_id: 0, + pro_forma_duration: '', + pro_forma_start_date: '', + fundDropDownId: 0, + fundDropDownValue: 'Select Fund', + fundOptions: null, +}; + +export default FinanceOverview; diff --git a/src/components/Blocnote/FinancialInputs/IncomeStatements.js b/src/components/Blocnote/FinancialInputs/IncomeStatements.js new file mode 100644 index 0000000000000000000000000000000000000000..04a58767ca3f7f2698952f7758954ef21a2ea507 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/IncomeStatements.js @@ -0,0 +1,608 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, + Button, + Input, + InputGroup, + InputGroupAddon, + Table, +} from 'reactstrap'; +import ResultMessage from './ResultMessage'; + + +class IncomeStatements extends Component { + constructor(props) { + super(props); + this.toggleGrowthRate = this.toggleGrowthRate.bind(this); + this.changeGrowthRate = this.changeGrowthRate.bind(this); + this.handleOnChange = this.handleOnChange.bind(this); + // this.handleOnChangeNew = this.handleOnChangeNew.bind(this); + + const obj = { + GRDropdownOpen: false, + GRDropdownValue: 'Select Growth Rate', + selectedDropdownId: 0, + growthRateOptions: [ + { id: '-2', key: '-2', name: 'CAGR=1.58%' }, + { id: '-1', key: '-1', name: 'Average' }, + { id: '0', key: '0', name: '0%' }, + { id: '1', key: '1', name: '1%' }, + { id: '2', key: '2', name: '2%' }, + { id: '3', key: '3', name: '3%' }, + { id: '4', key: '4', name: '4%' }, + { id: '5', key: '5', name: '5%' }, + { id: '6', key: '6', name: '6%' }, + { id: '7', key: '7', name: '7%' }, + { id: '8', key: '8', name: '8%' }, + { id: '9', key: '9', name: '9%' }, + { id: '10', key: '10', name: '10%' }, + { id: '11', key: '11', name: '11%' }, + { id: '12', key: '12', name: '12%' }, + { id: '13', key: '13', name: '13%' }, + { id: '14', key: '14', name: '14%' }, + { id: '15', key: '15', name: '15%' }, + ], + incomeStatements: props.data, + incomeStatementsNew: null, + error: false, + loading: false, + action: null, + }; + + const histYears = {}; + const incomeStatementsNew = { incomeStatementsNew: [] }; + if (props.data.hist !== null) { + Object.keys(props.data.hist).forEach((year, id) => { + const histYear = `Year-${id + 1}`; + histYears[histYear] = parseInt(year, 10); + }); + } else { + [0, 1, 2].forEach(id => { + const histYear = `Year-${id + 1}`; + histYears[histYear] = null; + }); + + const growthRate = {}; + growthRate['growth-rate'] = props.data.cagr; + incomeStatementsNew.incomeStatementsNew.push(this.initIncomeStatement()); + incomeStatementsNew.incomeStatementsNew.push(this.initIncomeStatement()); + incomeStatementsNew.incomeStatementsNew.push(this.initIncomeStatement()); + incomeStatementsNew.incomeStatementsNew.push(growthRate); + } + + this.state = Object.assign(obj, histYears, incomeStatementsNew); + console.log(this.state); // eslint-disable-line + } + + initIncomeStatement = () => { + const statement = { + year: null, + revenue: null, + }; + statement['utility-expense'] = null; + statement['non-utility-operating-expense'] = null; + return statement; + } + + handleOnChange = (event) => { + const year = event.target.id; + const targetName = event.target.name; + const copyIS = this.state.incomeStatements; + Object.entries(copyIS.hist).forEach((incomeStatement) => { + if (incomeStatement[0] === year) { + copyIS.hist[year][targetName] = parseFloat(event.target.value); + } + }); + + this.setState({ incomeStatements: copyIS }, () => { + console.log(this.state.incomeStatements); // eslint-disable-line + }); + } + + handleOnChangeNew = (event) => { + const id = event.target.id; + const targetName = event.target.name.includes('Year') ? 'year' : event.target.name; + const targetValue = event.target.value; + const copyISNew = this.state.incomeStatementsNew.map((item, key) => { + if (parseInt(key, 10) === parseInt(id, 10)) { + const tmp = item; + tmp[targetName] = targetValue; + return tmp; + } + return item; + }); + this.setState({ incomeStatementsNew: copyISNew }, () => { + console.log(this.state.incomeStatementsNew); // eslint-disable-line + }); + } + + toggleGrowthRate() { + this.setState({ + GRDropdownOpen: !this.state.GRDropdownOpen, + }); + } + + changeGrowthRate(e) { + this.setState({ GRDropdownValue: e.currentTarget.textContent }); + this.setState({ selectedDropdownId: e.currentTarget.id }); + } + + buildHeader = () => { + const headers = []; + const year = 'year'; + headers.push( + + Year + + ); + + Object.entries(this.state).forEach((entry, key) => { + if (entry[0].includes('Year')) { + headers.push( + + + + ); + } + }); + const futureYear = this.state.incomeStatements.future.year; + headers.push( + + {futureYear} + + ); + const average = 'average'; + headers.push( + + Average + + ); + return headers; + } + + buildEmptyHeader = () => { + const headers = []; + const year = 'year'; + headers.push( + + Year + + ); + let id = 0; + Object.entries(this.state).forEach((entry, key) => { + if (entry[0].includes('Year')) { + headers.push( + + + + ); + id += 1; + } + }); + headers.push( + + {' '}Next Year{' '} + + ); + headers.push( + + {' '}Average{' '} + + ); + return headers; + } + + handleUpdateIncomeStatements = () => { + this.setState({ loading: true }); + const hist = []; + Object.entries(this.state.incomeStatements.hist).forEach(incomeStatement => { + const obj = { + year: String(incomeStatement[0]), + revenue: String(incomeStatement[1].revenue), + }; + obj['non-utility-operating-expense'] = String(incomeStatement[1].non_utility_expense); + obj['utility-expense'] = String(incomeStatement[1].utility_expense); + hist.push(obj); + }); + const growthRate = {}; + growthRate['growth-rate'] = String(this.state.selectedDropdownId); + hist.push(growthRate); + this.props.updateIncomeStatements( + this.props.buildingId, + hist, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + + handleCreateIncomeStatements = () => { + const emptyMessages = []; + const incomeStatementsNew = Object.values(this.state.incomeStatementsNew.slice(0, 3)) + .map(statement => { + if (statement.year === null && + !emptyMessages.includes('Year')) { + emptyMessages.push('Year'); + } + if (statement.revenue === null && + !emptyMessages.includes('Revenue')) { + emptyMessages.push('Revenue'); + } + if (statement['utility-expense'] === null && + !emptyMessages.includes('Utility Expense')) { + emptyMessages.push('Utility Expense'); + } + if (statement['non-utility-operating-expense'] === null && + !emptyMessages.includes('Non-Utility Operating Expense')) { + emptyMessages.push('Non-Utility Operating Expense'); + } + + const newStatement = {}; + newStatement.year = String(statement.year); + newStatement.revenue = String(statement.revenue); + newStatement['utility-expense'] = String(statement['utility-expense']); + newStatement['non-utility-operating-expense'] = String(statement['non-utility-operating-expense']); + return newStatement; + }); + + if (this.state.GRDropdownValue === 'Select Growth Rate') { + emptyMessages.push('Growth Rate'); + } + + if (emptyMessages.length !== 0) { + const msg = `Please fill in ${emptyMessages.join(', ')} fields.`; + alert(msg); + } else { + const growthRate = {}; + growthRate['growth-rate'] = String(this.state.incomeStatementsNew[3]['growth-rate']); + incomeStatementsNew.push(growthRate); + this.setState({ loading: true }); + this.props.updateIncomeStatements( + this.props.buildingId, + incomeStatementsNew, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + } + + render() { + let header = []; + let rows = []; + let calculateSaveButton = null; + + const columnKeys = [ + 'revenue', + 'utility_expense', + 'energy_opex', + 'electric_opex', + 'gas_opex', + 'oil_opex', + 'water_opex', + 'other_utility', + 'non_utility_expense', + 'net_non_energy_opex', + 'total_opex', + 'noi', + ]; + + const columnNames = { + revenue: 'Revenue', + utility_expense: 'Utility Expense', + energy_opex: 'Energy Expense', + electric_opex: 'Electric Bill', + gas_opex: 'Gas Bill', + oil_opex: 'Oil Bill', + water_opex: 'Water Bill', + other_utility: 'Other Utility Expense', + non_utility_expense: 'Non-Utility Operating Expense', + net_non_energy_opex: 'Net Non-Energy Expense', + total_opex: 'Total Expense', + noi: 'Net Operating Income', + }; + + const growthRateOptions = this.state.growthRateOptions.map(e => { + return ( + + {e.name} + + ); + }); + + if (this.state.incomeStatements.hist !== null && + this.state.incomeStatements.future !== null) { + header = this.buildHeader(); + const histYears = Object.keys(this.state.incomeStatements.hist).sort(); + rows = columnKeys.map((columnKey, id) => { + const cells = [ + ...[columnNames[columnKey]], + ...histYears.map((histYear) => { + let cellValue = this.state.incomeStatements.hist[histYear][columnKey]; + cellValue = Math.round(cellValue * 100) / 100; + if (['utility_expense', 'revenue', 'non_utility_expense'].includes(columnKey)) { + return ( + + + $ + + + + ); + } + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + return cellValue; + }), + ...[this.state.incomeStatements.future[columnKey]].map((amount) => { + let cellValue = amount; + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + return cellValue; + }), + ...Object.values([this.state.incomeStatements.average[columnKey]]).map((amount) => { + let cellValue = amount; + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + return cellValue; + }), + ]; + + const cellsData = Object.values(cells).map((celldata, key) => { + return ( + + + {celldata} + + + ); + }); + + return ( + + {cellsData} + + ); + }); + + calculateSaveButton = ( + + ); + } else { + header = this.buildEmptyHeader(); + const ids = Object.keys(this.state.incomeStatementsNew).slice(0, 3).sort(); + rows = columnKeys.map((columnKey, id) => { + const cells = [ + ...[columnNames[columnKey]], + ...ids.map((index) => { + const keyMapping = { + revenue: 'revenue', + utility_expense: 'utility-expense', + non_utility_expense: 'non-utility-operating-expense', + }; + + if (['revenue', 'utility_expense', 'non_utility_expense'].includes(columnKey)) { + const newColumnKey = keyMapping[columnKey]; + return ( + + + $ + + + + ); + } + return null; + }), + ...['', ''], + ]; + + const cellsData = Object.values(cells).map((celldata, key) => { + return ( + + + {celldata} + + + ); + }); + + return ( + + {cellsData} + + ); + }); + + calculateSaveButton = ( + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.state.loading) { + messageContent = 'Processing ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.state.error && typeof this.state.error === 'object') { + messageContent = this.state.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.state.error && !this.state.loading + && this.state.incomeStatements !== null + && this.state.action !== null) { + messageContent = this.state.action.charAt(0).toUpperCase() + this.state.action.slice(1); + messageStyle = this.props.successMessageStyle; + } + + return ( +
+

+ Income Statements (in $, End of Year) +

+
+
+ + + {this.state.GRDropdownValue} + + + {growthRateOptions} + + +
+
+    + {calculateSaveButton} +
+
+
+
+ + + {header} + + + {rows} + +
+
+
+
+ ); + } +} + +IncomeStatements.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + buildingId: PropTypes.number, + data: PropTypes.shape({ + average: PropTypes.shape({ + electric_opex: PropTypes.number, + energy_opex: PropTypes.number, + gas_opex: PropTypes.number, + net_non_energy_opex: PropTypes.number, + noi: PropTypes.number, + non_utility_expense: PropTypes.number, + oil_opex: PropTypes.number, + other_utility: PropTypes.number, + revenue: PropTypes.number, + total_opex: PropTypes.number, + utility_expense: PropTypes.number, + water_opex: PropTypes.number, + year: null, + }), + cagr: PropTypes.number, + future: PropTypes.shape({ + electric_opex: PropTypes.number, + energy_opex: PropTypes.number, + gas_opex: PropTypes.number, + net_non_energy_opex: PropTypes.number, + noi: PropTypes.number, + non_utility_expense: PropTypes.number, + oil_opex: PropTypes.number, + other_utility: PropTypes.number, + revenue: PropTypes.number, + total_opex: PropTypes.number, + utility_expense: PropTypes.number, + water_opex: PropTypes.number, + year: null, + }), + growth_rate: PropTypes.number, + hist: PropTypes.objectOf, + result: PropTypes.arrayOf(PropTypes.shape({ + year: PropTypes.string, + revenue: PropTypes.string, + utility_expense: PropTypes.string, + non_utility_operating_expense: PropTypes.string, + })), + }), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + updateIncomeStatements: PropTypes.func, +}; + +export default IncomeStatements; diff --git a/src/components/Blocnote/FinancialInputs/LoanOptions.js b/src/components/Blocnote/FinancialInputs/LoanOptions.js new file mode 100644 index 0000000000000000000000000000000000000000..3b40ddb19f9aad2f9fa98024e33a417ff2269cf8 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/LoanOptions.js @@ -0,0 +1,331 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + Input, + Table, + InputGroup, + InputGroupAddon, +} from 'reactstrap'; +import LoanOptionsRow from './LoanOptionsRow'; +import ResultMessage from './ResultMessage'; + + +class LoanOptions extends Component { + constructor(props) { + super(props); + this.deleteRow = this.deleteRow.bind(this); + this.addRow = this.addRow.bind(this); + this.updateRow = this.updateRow.bind(this); + this.state = { + loanOptions: props.data, + lenders: props.lenders, + noi: props.noi, + cash: props.cash, + id: 99, + lender: null, + interest_rate: null, + duration: null, + max_loan_amount: null, + action: null, + }; + } + + validateInputs = () => { + const emptyFields = []; + if (this.state.loanOptions.length > 0) { + Object.values(this.state.loanOptions).forEach(loanOption => { + console.log(loanOption); // eslint-disable-line + if (loanOption.lender === null && + !emptyFields.includes('Lender')) { + emptyFields.push('Lender'); + } + if (loanOption.interest_rate === null && + !emptyFields.includes('Interest Rate')) { + emptyFields.push('Interest Rate'); + } + if (loanOption.duration === null && + !emptyFields.includes('Duration')) { + emptyFields.push('Duration'); + } + if (loanOption.max_loan_amount === null && + !emptyFields.includes('Max Loan Amount')) { + emptyFields.push('Max Loan Amount'); + } + }); + } + + if (emptyFields.length > 0) { + alert(`Please fill in ${emptyFields.join(', ')} fields`); + return false; + } + return true; + } + + deleteRow = id => { + if (this.state.loanOptions.length === 1) { + alert('Sorry, you need at least one loan option!'); + } else if (confirm('Are you sure to delete this loan option?') === true) { + const loanOptionsCopy = this.state.loanOptions.filter((loanOption, index) => { + console.log(loanOption); // eslint-disable-line + return index !== id; + }); + this.setState({ loanOptions: loanOptionsCopy }, () => { + console.log('Statement deleted!'); // eslint-disable-line + }); + } + } + + addRow = () => { + if (this.validateInputs() === true) { + if (this.props.financeOverviewExist === true) { + const loanOption = { + id: this.state.id, + lender: this.state.lender, + interest_rate: this.state.interest_rate, + duration: this.state.duration, + max_loan_amount: this.state.max_loan_amount, + }; + console.log(this.state.loanOptions); // eslint-disable-line + const loanOptions = this.state.loanOptions; + loanOptions.push(loanOption); + this.setState({ loanOptions }, () => { + console.log(this.state.loanOptions, 'New loan option added!'); // eslint-disable-line + this.setState({ + id: this.state.id + 1, + lender: null, + interest_rate: null, + duration: null, + max_loan_amount: null, + }); + }); + } else { + alert('Please fill in Financial Overview first'); + } + } + } + + updateRow = (row) => { + const loanOptions = this.state.loanOptions.map((loanOption, id) => { + console.log(id); // eslint-disable-line + console.log(row.id); // eslint-disable-line + if (id === row.id) { + const tmp = { id }; + tmp.lender = row.lender; + tmp.interest_rate = row.interest_rate; + tmp.duration = row.duration; + tmp.max_loan_amount = row.max_loan_amount; + return tmp; + } + return loanOption; + }); + this.setState({ loanOptions }, () => { + console.log(loanOptions); // eslint-disable-line + }); + } + + handleUpdateLoanOptions = () => { + if (this.validateInputs() === true) { + const formData = {}; + formData['noi-dscr'] = parseFloat(this.state.noi).toFixed(2); + formData['cash-dscr'] = parseFloat(this.state.cash).toFixed(2); + formData.instance = this.state.loanOptions; + this.props.updateLoanOptions( + this.props.buildingId, + formData, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + } + + render() { + const header = [ + 'Loan Options', + 'Lender', + 'Interest Rate', + 'Duration', + 'Maximum Loan Amount', + 'Option', + ].map((title, key) => { + return ( + + {title} + + ); + }); + + let loans = []; + let saveButton = null; + if (Object.keys(this.state.loanOptions).length !== 0) { + loans = this.state.loanOptions.map((loanOption, index) => { + return ( + + ); + }); + saveButton = ( + + ); + } else { + loans = ( + + + Currently no loans. + + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Updating ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.data !== null + && this.state.updated) { + messageContent = 'Saved!'; + messageStyle = this.props.successMessageStyle; + } + + return ( +
+

+ Loan Options +

+
+
+ + + + + + {header} + + + {loans} + +
+
+
+ + + Required NOI DSCR + + + +
+
+ + + Required Cash DSCR + + + +
+
+
+
+
+
+
+    +   + {saveButton} +
+
+
+ ); + } +} + +LoanOptions.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + cash: PropTypes.number, + noi: PropTypes.number, + lenders: PropTypes.arrayOf, + data: PropTypes.arrayOf( + PropTypes.shape({ + duration: PropTypes.string, + interest_rate: PropTypes.string, + lender: PropTypes.string, + max_loan_amount: PropTypes.string, + }), + ), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + buildingId: PropTypes.number, + financeOverviewExist: PropTypes.bool, + updateLoanOptions: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +export default LoanOptions; diff --git a/src/components/Blocnote/FinancialInputs/LoanOptionsRow.js b/src/components/Blocnote/FinancialInputs/LoanOptionsRow.js new file mode 100644 index 0000000000000000000000000000000000000000..bf54384b3dbc851e3f740e07a4c17b8b4ef942b8 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/LoanOptionsRow.js @@ -0,0 +1,160 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + InputGroup, + InputGroupAddon, + Input, + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, +} from 'reactstrap'; + + +class LoanOptionsRow extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(this); + this.onDelEvent = this.onDelEvent.bind(this); + this.toggleLender = this.toggleLender.bind(this); + this.changeLender = this.changeLender.bind(this); + this.state = { + id: props.id, + lender: props.lender, + lenders: props.lenders, + interest_rate: props.interestRate, + duration: props.duration, + max_loan_amount: props.maxLoanAmount, + lenderDropdownOpen: false, + lenderDropDownValue: props.lender, + lenderOptions: props.lenders !== undefined ? props.lenders.map((lender, id) => { + return { id, key: id, name: lender }; + }) : [], + }; + } + + onDelEvent() { + this.props.onDelEvent(this.props.id); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }, + () => { + console.log(this.state); // eslint-disable-line + this.props.onChangeEvent(this.state); + }); + } + + toggleLender() { + this.setState({ + lenderDropdownOpen: !this.state.lenderDropdownOpen, + }); + } + + changeLender(e) { + this.setState({ lenderDropDownValue: e.currentTarget.textContent }); + } + + render() { + const style = { textAlign: 'left' }; + const lenderOptions = this.state.lenderOptions.map(e => { + return ( + + {e.name} + + ); + }); + return ( + + + Loan {this.state.id + 1} + + + + + {this.state.lenderDropDownValue} + + + {lenderOptions} + + + + + + + + % + + + + + + + + Months + + + + + + + $ + + + + + + + + + ); + } +} + +LoanOptionsRow.propTypes = { + id: PropTypes.number, + lender: PropTypes.string, + interestRate: PropTypes.number, + duration: PropTypes.string, + maxLoanAmount: PropTypes.number, + onDelEvent: PropTypes.func, + lenders: PropTypes.arrayOf, + onChangeEvent: PropTypes.func, +}; + +export default LoanOptionsRow; diff --git a/src/components/Blocnote/FinancialInputs/MortgageLiabilities.js b/src/components/Blocnote/FinancialInputs/MortgageLiabilities.js new file mode 100644 index 0000000000000000000000000000000000000000..58d2c9b9dcce547673c55048167929551a07c2b6 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/MortgageLiabilities.js @@ -0,0 +1,272 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + Table, +} from 'reactstrap'; +import MortgageLiabilitiesRow from './MortgageLiabilitiesRow'; +import ResultMessage from './ResultMessage'; + + +class MortgageLiabilities extends Component { + constructor(props) { + super(props); + this.deleteRow = this.deleteRow.bind(this); + this.addRow = this.addRow.bind(this); + this.state = { + liabilities: this.props.data, + id: this.props.data.length, + lender: null, + input_date: null, + monthly_service: null, + remaining_term: null, + }; + } + + validateInputs = () => { + const emptyFields = []; + if (this.state.liabilities.length > 0) { + Object.values(this.state.liabilities).forEach(liability => { + console.log(liability); // eslint-disable-line + if (liability.lender === null && + !emptyFields.includes('Lender')) { + emptyFields.push('Lender'); + } + if (liability.monthly_service === null && + !emptyFields.includes('Monthly Service')) { + emptyFields.push('Monthly Service'); + } + if (liability.remaining_term === null && + !emptyFields.includes('Remaining Term')) { + emptyFields.push('Remaining Term'); + } + if (liability.input_date === null && + !emptyFields.includes('Input Date')) { + emptyFields.push('Input Date'); + } + }); + } + + if (emptyFields.length > 0) { + alert(`Please fill in ${emptyFields.join(', ')} fields`); + return false; + } + return true; + } + + deleteRow = id => { + if (confirm('Are you sure to delete this liability?') === true) { + const liabilitiesCopy = this.state.liabilities.filter((liability, index) => { + console.log(liability); // eslint-disable-line + return index !== id; + }); + this.setState({ liabilities: liabilitiesCopy }, () => { + console.log('Liability deleted!'); // eslint-disable-line + }); + } + } + + addRow = () => { + if (this.validateInputs() === true) { + const newLiability = { + id: this.state.id, + lender: this.state.lender, + input_date: this.state.input_date, + monthly_service: this.state.monthly_service, + remaining_term: this.state.remaining_term, + }; + const liabilitiesCopy = this.state.liabilities; + liabilitiesCopy.push(newLiability); + this.setState({ liabilities: liabilitiesCopy }, () => { + console.log(this.state.liabilities, 'New liability added!'); // eslint-disable-line + this.setState({ + id: this.state.id + 1, + lender: null, + input_date: null, + monthly_service: null, + remaining_term: null, + }); + }); + } + } + + updateRow = (row) => { + const liabilities = this.state.liabilities.map((liability, id) => { + console.log(id); // eslint-disable-line + console.log(row.id); // eslint-disable-line + if (id === row.id) { + const tmp = { id }; + tmp.lender = row.lender; + tmp.input_date = row.input_date; + tmp.monthly_service = row.monthly_service; + tmp.remaining_term = row.remaining_term; + return tmp; + } + return liability; + }); + this.setState({ liabilities }, () => { + console.log(liabilities); // eslint-disable-line + }); + } + + handleUpdateLiabilities = () => { + if (this.validateInputs() === true) { + this.props.updateLiabilities( + this.props.buildingId, + this.state.liabilities, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + } + + render() { + const header = [ + '#Debt', + 'Lender Name', + 'Monthly Service', + 'Remaining Terms', + 'Input Date', + 'Option', + ].map((title, key) => { + return ( + + {title} + + ); + }); + + let rows = []; + let saveButton = null; + if (this.state.liabilities.length !== 0) { + console.log(this.state.liabilities); // eslint-disable-line + rows = this.state.liabilities.map((liability, index) => { + return ( + + ); + }); + saveButton = ( + + ); + } else { + rows = ( + + + Currently no liabilities. + + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Updating ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.data !== null + && this.state.updated) { + messageContent = 'Saved!'; + messageStyle = this.props.successMessageStyle; + } + + return ( +
+

+ Mortgage and Liabilities +

+
+
+ + + {header} + + + {rows} + +
+
+
+
+
+    +   + {saveButton} +
+
+
+ ); + } +} + +MortgageLiabilities.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + data: PropTypes.arrayOf( + PropTypes.shape({ + lender: PropTypes.string, + monthly_service: PropTypes.boolean, + input_date: PropTypes.string, + remaining_term: PropTypes.string, + }), + ), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + buildingId: PropTypes.number, + updateLiabilities: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +export default MortgageLiabilities; diff --git a/src/components/Blocnote/FinancialInputs/MortgageLiabilitiesRow.js b/src/components/Blocnote/FinancialInputs/MortgageLiabilitiesRow.js new file mode 100644 index 0000000000000000000000000000000000000000..fbb2b838139b9437499d42263b56ba439e10213d --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/MortgageLiabilitiesRow.js @@ -0,0 +1,120 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + InputGroup, + InputGroupAddon, + Input, +} from 'reactstrap'; + + +class MortgageLiabilitiesRow extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(this); + this.onDelEvent = this.onDelEvent.bind(this); + this.state = { + id: props.id, + lender: props.lender, + input_date: props.inputDate, + monthly_service: props.monthlyService, + remaining_term: props.remainingTerm, + }; + } + + onDelEvent() { + this.props.onDelEvent(this.props.id); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }, + () => { + console.log(this.state); // eslint-disable-line + this.props.onChangeEvent(this.state); + }); + } + + render() { + const style = { textAlign: 'left' }; + return ( + + + {this.props.id + 1} + + + + + + + + $ + + + + + + + + + Months + + + + + + + + + + + ); + } +} + +MortgageLiabilitiesRow.propTypes = { + id: PropTypes.number, + lender: PropTypes.string, + inputDate: PropTypes.string, + monthlyService: PropTypes.string, + remainingTerm: PropTypes.string, + onDelEvent: PropTypes.func, + onChangeEvent: PropTypes.func, +}; + +export default MortgageLiabilitiesRow; diff --git a/src/components/Blocnote/FinancialInputs/ResultMessage.js b/src/components/Blocnote/FinancialInputs/ResultMessage.js new file mode 100644 index 0000000000000000000000000000000000000000..22e20eea442a147cb6d5f14fc85ed4b5c97e7574 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/ResultMessage.js @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +const ResultMessage = (props) => { + return ( + + {props.messageContent} + + ); +}; + +ResultMessage.propTypes = { + messageStyle: PropTypes.shape({ + downpayment: PropTypes.string, + expected_net_noi_dscr: PropTypes.string, + expected_payback: PropTypes.string, + }), + messageContent: PropTypes.string, +}; + +export default ResultMessage; diff --git a/src/components/Blocnote/FinancialInputs/csrftoken.js b/src/components/Blocnote/FinancialInputs/csrftoken.js new file mode 100644 index 0000000000000000000000000000000000000000..7c7bc44c5d20c8c439db5b956fe50f625c5ed3a3 --- /dev/null +++ b/src/components/Blocnote/FinancialInputs/csrftoken.js @@ -0,0 +1,25 @@ +import React from 'react'; + +const getCookie = (name) => { + let cookieValue = null; + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i += 1) { + let cookie = cookies[i]; + cookie = cookie.trim(); + // console.log(cookie); // eslint-disable-line + if (cookie.substring(0, name.length + 1) === `${name}=`) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +}; +const csrftoken = getCookie('csrftoken'); +const CSRFToken = () => { + return ( + + ); +}; +export default CSRFToken; diff --git a/src/components/Blocnote/PreliminaryFinance/BudgetChart.js b/src/components/Blocnote/PreliminaryFinance/BudgetChart.js new file mode 100644 index 0000000000000000000000000000000000000000..53c0d6000d1182e5b3b4ced917e039c82bfebd9c --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/BudgetChart.js @@ -0,0 +1,90 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + LineChart, + Line, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + + +class BudgetChart extends Component { + constructor(props) { + super(props); + + this.state = { + successMessages: [], + error: false, + loading: false, + }; + } + + render() { + const data = []; + this.props.savingPotentialList.forEach((saving, index) => { + data.push({ + x_name: `${saving * 100}%`, + loan_only: this.props.budgets[0][index], + loan_first: this.props.budgets[1][index], + sf_first: this.props.budgets[2][index], + sf_max: this.props.budgets[3][index], + }); + }); + + const CustomizedLabel = React.createClass({ + render() { + const { x, y, stroke, value } = this.props; + + return ( + + {value} + + ); + }, + }); + + const renderLineChart = ( + + + + + + + } /> + + + + + + + ); + + return renderLineChart; + } +} + +BudgetChart.propTypes = { + // blockStyle: PropTypes.string, + // headerStyle: PropTypes.string, + x: PropTypes.number, + y: PropTypes.number, + stroke: PropTypes.number, + value: PropTypes.number, + savingPotentialList: PropTypes.arrayOf, + budgets: PropTypes.arrayOf, +}; + +export default BudgetChart; diff --git a/src/components/Blocnote/PreliminaryFinance/CostEstimation.js b/src/components/Blocnote/PreliminaryFinance/CostEstimation.js new file mode 100644 index 0000000000000000000000000000000000000000..6f561c3613d6e1d7bd90e5e8a7bce0d019727511 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/CostEstimation.js @@ -0,0 +1,169 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Table, + Button, +} from 'reactstrap'; +import CostEstimationRow from './CostEstimationRow'; + + +class CostEstimation extends Component { + constructor(props) { + super(props); + this.addRow = this.addRow.bind(); + this.state = { + costEstimation: props.data.map((estimation) => { + return { item: estimation.cost_item, cost: String(estimation.cost) }; + }), + id: props.data.length, + cost_item: null, + cost: null, + }; + } + + validateInputs = () => { + const emptyFields = []; + if (this.state.costEstimation.length > 0) { + Object.values(this.state.costEstimation).forEach(estimation => { + if (estimation.item === null && + !emptyFields.includes('Item name')) { + emptyFields.push('Item name'); + } + if (estimation.cost === null && + !emptyFields.includes('Cost')) { + emptyFields.push('Cost'); + } + }); + } + + if (emptyFields.length > 0) { + alert(`Please fill in ${emptyFields.join(', ')} fields`); + return false; + } + return true; + } + + addRow = () => { + if (this.validateInputs() === true) { + const newEstimation = { + item: this.state.item, + cost: this.state.cost, + }; + const costEstimation = this.state.costEstimation; + costEstimation.push(newEstimation); + this.setState({ costEstimation }, () => { + console.log(this.state.costEstimation, 'New cost estimation added!'); // eslint-disable-line + this.setState({ + cost_item: null, + cost: null, + }); + }); + } + } + + delRow = id => { + if (confirm('Are you sure to delete this estimation?') === true) { + const costEstimation = this.state.costEstimation.filter((estimation, index) => { + console.log(estimation); // eslint-disable-line + return index !== id; + }); + this.setState({ costEstimation }, () => { + console.log('Estimation deleted!'); // eslint-disable-line + }); + } + } + + updRow = (row) => { + const costEstimation = this.state.costEstimation.map((estimation, index) => { + if (index === row.id) { + return { item: row.item, cost: String(row.cost) }; + } + return estimation; + }); + this.setState({ costEstimation }, () => { + this.props.updateCostEstimation(this.state.costEstimation); + console.log(costEstimation); // eslint-disable-line + }); + } + + render() { + let rows = []; + const tdStyle = { + textAlign: 'center', + padding: '10px 0 10px', + fontSize: '13px', + }; + + if (this.state.costEstimation !== null) { + rows = this.state.costEstimation.map((costItem, id) => { + return ( + + ); + }); + } + + return ( +
+ + + + + + + + + + + {rows} + + + + + + +
+ Cost Estimation +
ItemEstimated CostOption
+ +
+
+ ); + } +} + +CostEstimation.propTypes = { + blockStyle: PropTypes.string, + // headerStyle: PropTypes.string, + data: PropTypes.arrayOf, + updateCostEstimation: PropTypes.func, +}; + +export default CostEstimation; diff --git a/src/components/Blocnote/PreliminaryFinance/CostEstimationRow.js b/src/components/Blocnote/PreliminaryFinance/CostEstimationRow.js new file mode 100644 index 0000000000000000000000000000000000000000..bf270674020ef5178cd0bf7ef8a56b08ab133e17 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/CostEstimationRow.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Input, + Button, +} from 'reactstrap'; + +class CostEstimationRow extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(); + this.handleDelRow = this.handleDelRow.bind(); + this.state = { + id: props.id, + item: props.item, + cost: props.cost, + }; + } + + handleDelRow = () => { + this.props.onDelEvent(this.props.id); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }, () => { + this.props.onUpdEvent(this.state); + }); + } + + render() { + return ( + + + + + +
+
$
+ +
+ + + + + + ); + } +} + +CostEstimationRow.propTypes = { + id: PropTypes.number, + item: PropTypes.string, + cost: PropTypes.number, + onUpdEvent: PropTypes.func, + onDelEvent: PropTypes.func, +}; + +export default CostEstimationRow; diff --git a/src/components/Blocnote/PreliminaryFinance/DownPayment.js b/src/components/Blocnote/PreliminaryFinance/DownPayment.js new file mode 100644 index 0000000000000000000000000000000000000000..5443c29f3fe15ac3de0ee33c7683cc28e83e6755 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/DownPayment.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + + +class DownPayment extends Component { + constructor(props) { + super(props); + + this.state = { + successMessages: [], + error: false, + loading: false, + }; + } + + render() { + return ( +
+

+ Downpayment +

+
+ {this.props.paymentAmount} +
+
+ ); + } +} + +DownPayment.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + paymentAmount: PropTypes.string, +}; + +export default DownPayment; diff --git a/src/components/Blocnote/PreliminaryFinance/InputScenario.js b/src/components/Blocnote/PreliminaryFinance/InputScenario.js new file mode 100644 index 0000000000000000000000000000000000000000..a31f7c5a5cc520d6587bbb0c507f7ea1587a8c68 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/InputScenario.js @@ -0,0 +1,164 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Input, + Button, +} from 'reactstrap'; +import CostEstimation from './CostEstimation'; +import SavingEstimation from './SavingEstimation'; +import ResultMessage from './../../../components/Blocnote/FinancialInputs/ResultMessage'; + +class InputScenario extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(); + this.state = { + scenarioName: props.scenarioName, + costs: props.costs, + savings: props.savings, + loading: false, + action: null, + }; + } + + handleUpdateScenario = () => { + const data = { + scenario: this.state.scenarioName, + cost: this.state.costs, + savings: Object.values(this.state.savings), + }; + console.log(data); // eslint-disable-line + this.props.updateScenario( + this.props.buildingId, + data, + ); + this.setState({ action: 'updated' }); + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: event.target.value }); + } + + handleUpdateCostEstimation = (costEstiomation) => { + this.setState({ costs: costEstiomation }, () => { + console.log(this.state.costs); // eslint-disable-line + }); + } + + handleUpdateSavingEstimation = (savingEstiomation) => { + this.setState({ savings: savingEstiomation }, () => { + console.log(this.state.savings); // eslint-disable-line + }); + } + + render() { + let messageStyle = {}; + let messageContent = null; + + if (this.props.loading) { + messageContent = 'Processing ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.props.error && typeof this.props.error === 'object') { + messageContent = this.props.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.props.error && !this.props.loading + && this.props.data !== null + && this.state.action !== null) { + messageContent = this.state.action.charAt(0).toUpperCase() + this.state.action.slice(1); + messageStyle = this.props.successMessageStyle; + } + + return ( +
+
+
+
+
Scenario :
+ +
+
+
+ {' '}{' '} + + + {' '} + +
+
+
+
+
+ +
+
+ +
+
+
+ ); + } +} + +InputScenario.propTypes = { + buildingId: PropTypes.number, + scenarioName: PropTypes.string, + data: PropTypes.objectOf, + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + costs: PropTypes.objectOf, + savings: PropTypes.objectOf, + updateScenario: PropTypes.func, + error: PropTypes.bool, + loading: PropTypes.bool, +}; + +export default InputScenario; diff --git a/src/components/Blocnote/PreliminaryFinance/LoanSummary.js b/src/components/Blocnote/PreliminaryFinance/LoanSummary.js new file mode 100644 index 0000000000000000000000000000000000000000..eca34032e9a58c48c1f51cfc963e4bb2321b6873 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/LoanSummary.js @@ -0,0 +1,65 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; +import LoanSummaryRow from './LoanSummaryRow'; + + +const LoanSummary = (props) => { + let header = []; + let rows = []; + + if (props.data !== null && props.data !== undefined) { + header = props.data[0].map((item) => { + return ( + {item} + ); + }); + props.data.shift(); + rows = props.data.map((item) => { + let amountBorrowed = Math.round(item[1] * 100) / 100; + amountBorrowed = amountBorrowed.toLocaleString(); + let amountUpperLimit = Math.round(item[2] * 100) / 100; + amountUpperLimit = amountUpperLimit.toLocaleString(); + const annualInterestRate = item[3][1] + item[3][0]; + const duration = `${item[4][1]} ${item[4][0]}`; + const monthlyDebtService = item[5].toLocaleString(); + return ( + + ); + }); + } + + return ( +
+

+ Loan Summary +

+ + + {header} + + + {rows} + +
+
+ ); +}; + +LoanSummary.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + data: PropTypes.arrayOf, +}; + +export default LoanSummary; diff --git a/src/components/Blocnote/PreliminaryFinance/LoanSummaryRow.js b/src/components/Blocnote/PreliminaryFinance/LoanSummaryRow.js new file mode 100644 index 0000000000000000000000000000000000000000..2b3da2b25fed6fc54d9aa392c0a573f29f7e3871 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/LoanSummaryRow.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +const LoanSummaryRow = (props) => { + return ( + + + {props.lender} + + + ${props.amountBorrowed} + + + ${props.amountUpperLimit} + + + {props.annualInterestRate} + + + {props.duration} + + + ${props.monthlyDebtService} + + + ); +}; + +LoanSummaryRow.propTypes = { + lender: PropTypes.string, + amountBorrowed: PropTypes.number, + amountUpperLimit: PropTypes.number, + annualInterestRate: PropTypes.number, + duration: PropTypes.number, + monthlyDebtService: PropTypes.number, +}; + +export default LoanSummaryRow; diff --git a/src/components/Blocnote/PreliminaryFinance/PostRetrofitBalanceSheet.js b/src/components/Blocnote/PreliminaryFinance/PostRetrofitBalanceSheet.js new file mode 100644 index 0000000000000000000000000000000000000000..e27c3f5f7da3b13f63c790e439ac3cb69f6bd0f1 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/PostRetrofitBalanceSheet.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; +import TableContent from './TableContent'; + + +const PostRetrofitBalanceSheet = (props) => { + let header = []; + + if (props.data !== null) { + header = props.data[0].map((item) => { + return ( + {item} + ); + }); + props.data.shift(); + } + + return ( +
+

+ Post Retrofit Balance Sheet +

+ + + + {header} + + + +
+
+ ); +}; + +PostRetrofitBalanceSheet.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + data: PropTypes.arrayOf, +}; + +export default PostRetrofitBalanceSheet; diff --git a/src/components/Blocnote/PreliminaryFinance/PostRetrofitIncomeStatement.js b/src/components/Blocnote/PreliminaryFinance/PostRetrofitIncomeStatement.js new file mode 100644 index 0000000000000000000000000000000000000000..c949d8967f9c130d6e8decc56de6d217e037b6c8 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/PostRetrofitIncomeStatement.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; +import TableContent from './TableContent'; + + +const PostRetrofitIncomeStatement = (props) => { + let header = []; + + if (props.data !== null) { + header = props.data[0].map((item) => { + return ( + {item} + ); + }); + props.data.shift(); + } + + return ( +
+

+ Post Retrofit Income Statement +

+ + + + {header} + + + +
+
+ ); +}; + +PostRetrofitIncomeStatement.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + data: PropTypes.arrayOf, +}; + +export default PostRetrofitIncomeStatement; diff --git a/src/components/Blocnote/PreliminaryFinance/PriorRetrofitBalanceSheet.js b/src/components/Blocnote/PreliminaryFinance/PriorRetrofitBalanceSheet.js new file mode 100644 index 0000000000000000000000000000000000000000..cde948567212f1ed1c7317003a214e4bf88c2bf7 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/PriorRetrofitBalanceSheet.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; +import TableContent from './TableContent'; + + +const PriorRetrofitBalanceSheet = (props) => { + let header = []; + + if (props.data !== null) { + header = props.data[0].map((item) => { + return ( + {item} + ); + }); + props.data.shift(); + } + + return ( +
+

+ Prior Retrofit Balance Sheet +

+ + + + {header} + + + +
+
+ ); +}; + +PriorRetrofitBalanceSheet.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + data: PropTypes.arrayOf, +}; + +export default PriorRetrofitBalanceSheet; diff --git a/src/components/Blocnote/PreliminaryFinance/PriorRetrofitIncomeStatement.js b/src/components/Blocnote/PreliminaryFinance/PriorRetrofitIncomeStatement.js new file mode 100644 index 0000000000000000000000000000000000000000..0a03858103592702d1f02e561cb825d3e126498f --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/PriorRetrofitIncomeStatement.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; +import TableContent from './TableContent'; + + +const PriorRetrofitIncomeStatement = (props) => { + let header = []; + + if (props.data !== null) { + header = props.data[0].map((item) => { + return ( + {item} + ); + }); + props.data.shift(); + } + + return ( +
+

+ Prior Retrofit Income Statement +

+ + + + {header} + + + +
+
+ ); +}; + +PriorRetrofitIncomeStatement.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + data: PropTypes.arrayOf, +}; + +export default PriorRetrofitIncomeStatement; diff --git a/src/components/Blocnote/PreliminaryFinance/ProjectEconomics.js b/src/components/Blocnote/PreliminaryFinance/ProjectEconomics.js new file mode 100644 index 0000000000000000000000000000000000000000..9332a32d777ab330fe4278b26edfcde8539a8c86 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/ProjectEconomics.js @@ -0,0 +1,51 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ProjectEconomicsRow from './ProjectEconomicsRow'; + +class ProjectEconomics extends Component { + constructor(props) { + super(props); + this.state = { + successMessages: [], + }; + } + + render() { + let rows = []; + + if (this.props.data !== null) { + rows = this.props.data.filter((item) => { + if (item.length === 0) { + return false; // skip + } + return true; + }).map((item) => { + return ( + + ); + }); + } + + return ( +
+

+ Project Economics +

+ + {rows} +
+
+ ); + } +} + +ProjectEconomics.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + data: PropTypes.arrayOf, +}; + +export default ProjectEconomics; diff --git a/src/components/Blocnote/PreliminaryFinance/ProjectEconomicsRow.js b/src/components/Blocnote/PreliminaryFinance/ProjectEconomicsRow.js new file mode 100644 index 0000000000000000000000000000000000000000..5315d9b9ae14fbce39551afd17030261e7bb2ae1 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/ProjectEconomicsRow.js @@ -0,0 +1,35 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + + +class ProjectEconomicsRow extends Component { + constructor(props) { + super(props); + + this.state = { + successMessages: [], + error: false, + loading: false, + }; + } + + render() { + return ( + + + {this.props.row_name} + + + {this.props.amount} + + + ); + } +} + +ProjectEconomicsRow.propTypes = { + row_name: PropTypes.string, + amount: PropTypes.number, +}; + +export default ProjectEconomicsRow; diff --git a/src/components/Blocnote/PreliminaryFinance/SavingEstimation.js b/src/components/Blocnote/PreliminaryFinance/SavingEstimation.js new file mode 100644 index 0000000000000000000000000000000000000000..904edc0c962e88c7965d044ef15d4e97586d1844 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/SavingEstimation.js @@ -0,0 +1,92 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Table, +} from 'reactstrap'; +import SavingEstimationRow from './SavingEstimationRow'; + + +class SavingEstimation extends Component { + constructor(props) { + super(props); + this.state = { + savingEstimation: props.data, + }; + } + + updRow = (row) => { + const newRow = row; + const savingEstimation = this.state.savingEstimation; + newRow.estimated_savings = String(row.estimated_savings); + savingEstimation[row.utilityType] = newRow; + this.setState({ savingEstimation }, () => { + this.props.updateSavingEstimation(this.state.savingEstimation); + console.log(this.state.savingEstimation); // eslint-disable-line + }); + } + + render() { + const tdStyle = { + textAlign: 'center', + padding: '10px 0 10px', + fontSize: '13px', + }; + const rows = []; + console.log(this.state.savingEstimation); // eslint-disable-line + + if (this.state.savingEstimation !== null) { + Object.entries(this.state.savingEstimation).forEach(([utilityName, utilityData]) => { + rows.push( + + ); + }); + } + + return ( +
+ + + + + + + + + + + + {rows} + +
+ Saving Estimation +
UtilityEstimated SavingsUsed Before RetrofitUsed After Retrofit
+
+ ); + } +} + +SavingEstimation.propTypes = { + blockStyle: PropTypes.string, + // headerStyle: PropTypes.string, + data: PropTypes.objectOf, + updateSavingEstimation: PropTypes.func, +}; + +export default SavingEstimation; diff --git a/src/components/Blocnote/PreliminaryFinance/SavingEstimationRow.js b/src/components/Blocnote/PreliminaryFinance/SavingEstimationRow.js new file mode 100644 index 0000000000000000000000000000000000000000..fe346d30ac7d7b6bb8355f787e379e7ff9586c43 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/SavingEstimationRow.js @@ -0,0 +1,94 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + FormGroup, + Label, + Input, +} from 'reactstrap'; + + +class SavingEstimationRow extends Component { + constructor(props) { + super(props); + this.handleOnChange = this.handleOnChange.bind(); + this.state = { + estimatedSavings: props.estimatedSavings, + usedBeforeRetrofit: props.usedBeforeRetrofit, + usedAfterRetrofit: props.usedAfterRetrofit, + utilityType: props.utilityType, + }; + console.log(props); // eslint-disable-line + } + + handleOnChange = (event) => { + this.setState({ [event.target.name]: + ['usedBeforeRetrofit', 'usedAfterRetrofit'].includes(event.target.name) ? + event.target.checked : event.target.value }, + () => { + this.props.updRow(this.state); + }); + } + + render() { + console.log(this.state); // eslint-disable-line + return ( + + + {this.props.utilityName} + + +
+ +
%
+
+ + + + + + + + + + + + + ); + } +} + +SavingEstimationRow.propTypes = { + tdStyle: PropTypes.string, + utilityType: PropTypes.string, + utilityName: PropTypes.string, + estimatedSavings: PropTypes.number, + usedBeforeRetrofit: PropTypes.bool, + usedAfterRetrofit: PropTypes.bool, + updRow: PropTypes.func, +}; + +export default SavingEstimationRow; diff --git a/src/components/Blocnote/PreliminaryFinance/SavingsScheduleChart.js b/src/components/Blocnote/PreliminaryFinance/SavingsScheduleChart.js new file mode 100644 index 0000000000000000000000000000000000000000..e548f3326698b2fa1b2d4029cab08a92de9eaf8f --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/SavingsScheduleChart.js @@ -0,0 +1,76 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + BarChart, + Bar, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + + +class SavingsScheduleChart extends Component { + constructor(props) { + super(props); + this.state = { + successMessages: [], + error: false, + loading: false, + }; + } + + render() { + const data = []; + const chartData = this.props.chartData; + chartData.year_list.forEach((year, index) => { + data.push({ + year_label: year, + net_savings_list: chartData.net_savings_list[index], + total_loan_list: chartData.total_loan_list[index], + energy_expense_list: chartData.energy_expense_list[index], + }); + }); + + const renderBarChart = ( +
+

+ Energy Expense Savings Projection +

+ + + + + + + + + + + + +
+ ); + + return renderBarChart; + } +} + +SavingsScheduleChart.propTypes = { + blockStyle: PropTypes.string, + headerStyle: PropTypes.string, + chartData: PropTypes.objectOf, +}; + +export default SavingsScheduleChart; diff --git a/src/components/Blocnote/PreliminaryFinance/SelectScenario.js b/src/components/Blocnote/PreliminaryFinance/SelectScenario.js new file mode 100644 index 0000000000000000000000000000000000000000..44ac0af7ada2861b72a005a55bddd3595c1c892e --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/SelectScenario.js @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +import { + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, +} from 'reactstrap'; + + +class SelectScenario extends Component { + constructor(props) { + super(props); + this.toggleScenario = this.toggleScenario.bind(this); + this.changeScenario = this.changeScenario.bind(this); + this.state = { + successMessages: [], + error: false, + loading: false, + scenarioDropdownOpen: false, + scenarioDropDownValue: 'Select Scenario', + scenarioOptions: [ + { id: 'Scenario1', key: 'Scenario1', name: 'Scenario 1' }, + { id: 'Scenario2', key: 'Scenario2', name: 'Scenario 2' }, + ], + }; + } + + toggleScenario() { + this.setState({ + scenarioDropdownOpen: !this.state.scenarioDropdownOpen, + }); + } + + changeScenario(e) { + this.setState({ scenarioDropDownValue: e.currentTarget.textContent }); + } + + render() { + const scenarioOptions = this.state.scenarioOptions.map(e => { + return ( + + {e.name} + + ); + }); + return ( +
+ + + {this.state.scenarioDropDownValue} + + + {scenarioOptions} + + +
+
+ ); + } +} + +export default SelectScenario; diff --git a/src/components/Blocnote/PreliminaryFinance/TableContent.js b/src/components/Blocnote/PreliminaryFinance/TableContent.js new file mode 100644 index 0000000000000000000000000000000000000000..8d0c0b918f657cb1a28f0b025108c3c824d0f859 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/TableContent.js @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +const TableContent = (props) => { + let rows = []; + + if (props.rows !== null) { + rows = props.rows.map((items) => { + const cells = items.map((item) => { + let cellValue = item; + if (typeof (item) !== 'string') { + cellValue = Math.round(item * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + } + + return ( + + + {cellValue} + + + ); + }); + + return ( + + {cells} + ); + }); + } + + return ( + + {rows} + + ); +}; + +TableContent.propTypes = { + rows: PropTypes.arrayOf, +}; + +export default TableContent; diff --git a/src/components/Blocnote/PreliminaryFinance/test.js b/src/components/Blocnote/PreliminaryFinance/test.js new file mode 100644 index 0000000000000000000000000000000000000000..ba2a8bafb90ff86283e2a7f0cc16afd37a81cac0 --- /dev/null +++ b/src/components/Blocnote/PreliminaryFinance/test.js @@ -0,0 +1,53 @@ +import React, { PureComponent } from 'react'; +import { + BarChart, Bar, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, +} from 'recharts'; + +const data = [ + { + name: 'Page A', uv: 4000, pv: 2400, amt: 2400, + }, + { + name: 'Page B', uv: 3000, pv: 1398, amt: 2210, + }, + { + name: 'Page C', uv: 2000, pv: 9800, amt: 2290, + }, + { + name: 'Page D', uv: 2780, pv: 3908, amt: 2000, + }, + { + name: 'Page E', uv: 1890, pv: 4800, amt: 2181, + }, + { + name: 'Page F', uv: 2390, pv: 3800, amt: 2500, + }, + { + name: 'Page G', uv: 3490, pv: 4300, amt: 2100, + }, +]; + +export default class Example extends PureComponent { + static jsfiddleUrl = 'https://jsfiddle.net/alidingling/30763kr7/'; + + render() { + return ( + + + + + + + + + + ); + } +} diff --git a/src/components/Blocnote/styles.css b/src/components/Blocnote/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..e6249044b459f8a87ac5ea47970b50ac1a55735a --- /dev/null +++ b/src/components/Blocnote/styles.css @@ -0,0 +1,14 @@ +.top-notification { + position: fixed; + z-index: 1060; + top: 0; + left: 0; + right: 0; + text-align: center; + overflow: hidden; +} + +.top-notification-item { + border-radius: 0px !important; + line-height: 2; +} diff --git a/src/components/SideBarDetail/index.js b/src/components/SideBarDetail/index.js index bf06ec463dee55120fc4fb00d00088f2b9c0e5ea..d648702f89cab73b7a23fc7594e63f2a4f295a32 100644 --- a/src/components/SideBarDetail/index.js +++ b/src/components/SideBarDetail/index.js @@ -210,14 +210,11 @@ export default function SideBarDetail({ building, children, contacts, user }) { Reports -
  • - +
  • + {blocnoteSVG} Blocnote - +
  • diff --git a/src/containers/BGroup/BGroupBuildingTable.js b/src/containers/BGroup/BGroupBuildingTable.js index d44d918138daac947a107f3ee8ac1970bb7c5a8d..018de06e0704c4f749dfff6ca443098e6a8bc1e2 100644 --- a/src/containers/BGroup/BGroupBuildingTable.js +++ b/src/containers/BGroup/BGroupBuildingTable.js @@ -1,11 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { CSVLink } from 'react-csv'; import ReactTable from 'react-table'; import ReactTooltip from 'react-tooltip'; import { Link } from 'react-router'; import { Icon } from 'react-fa'; import ReactGA from 'react-ga'; +import { CSVLink } from 'react-csv'; import { Input, Collapse, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, diff --git a/src/containers/Blocnote/BudgetSimulator/actions.js b/src/containers/Blocnote/BudgetSimulator/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..157a250fd5448e30c94dab5e3a9bcb30a600c06e --- /dev/null +++ b/src/containers/Blocnote/BudgetSimulator/actions.js @@ -0,0 +1,6 @@ +import * as constants from './constants'; +import { makeActionCreator } from '../../../utils/reduxHelpers'; + +export const loadBudgetSimulator = makeActionCreator(constants.BUDGET_SIMULATOR_REQUESTED, 'buildingId'); +export const budgetSimulatorLoaded = makeActionCreator(constants.BUDGET_SIMULATOR_SUCCEEDED, 'instance'); +export const budgetSimulatorFailed = makeActionCreator(constants.BUDGET_SIMULATOR_FAILED, 'error'); diff --git a/src/containers/Blocnote/BudgetSimulator/constants.js b/src/containers/Blocnote/BudgetSimulator/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..c0dbcb69bd2310d6314085618801271d4274bd10 --- /dev/null +++ b/src/containers/Blocnote/BudgetSimulator/constants.js @@ -0,0 +1,3 @@ +export const BUDGET_SIMULATOR_REQUESTED = 'BUDGET_SIMULATOR_REQUESTED'; +export const BUDGET_SIMULATOR_SUCCEEDED = 'BUDGET_SIMULATOR_SUCCEEDED'; +export const BUDGET_SIMULATOR_FAILED = 'BUDGET_SIMULATOR_FAILED'; diff --git a/src/containers/Blocnote/BudgetSimulator/index.js b/src/containers/Blocnote/BudgetSimulator/index.js new file mode 100644 index 0000000000000000000000000000000000000000..00b40c5aa2aec6d03cac1a6b5a27466f1bc52c83 --- /dev/null +++ b/src/containers/Blocnote/BudgetSimulator/index.js @@ -0,0 +1,118 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import LinkBarDetail from '../../../components/LinkBarDetail'; +import buildingDetailPropTypes from '../../Building/propTypes'; +import './../styles.css'; +import Loading from '../../../components/Loading'; +import BugetTable from '../../../components/Blocnote/BudgetSimulator/BugetTable'; +import BudgetChart from '../../../components/Blocnote/BudgetSimulator/BudgetChart'; +import { loadBudgetSimulator } from './actions'; +import blocnoteProps from './../propTypes'; + + +class BudgetSimulator extends Component { + componentDidMount() { + this.props.loadBudgetSimulator(this.props.building.building_id); + } + + processData = (data) => { + console.log(data); // eslint-disable-line + const tables = data.instance.tables; + return { + savingPotentialList: data.instance.saving_potential_list, + budgetLoanFirst: data.instance.tables.budget_loan_first, + budgetLoanOnly: data.instance.tables.budget_loan_only, + budgetSfFirst: data.instance.tables.budget_sf_first, + budgetSfMax: data.instance.tables.budget_sf_max, + budgets: Object.keys(tables).map(tableName => tables[tableName][0].slice(1)), + }; + } + + render() { + let mainContent = null; + let bugetTables = []; + const blockStyle = { marginBottom: '40px' }; + const tableKeys = [ + 'budgetLoanFirst', + 'budgetLoanOnly', + 'budgetSfFirst', + 'budgetSfMax', + ]; + const tableNames = { + budgetLoanFirst: 'Budget with Loan only', + budgetLoanOnly: 'Budget with Loan first', + budgetSfFirst: 'Budget with SF first', + budgetSfMax: 'Budget with SF maximum', + }; + + const { blocnote } = this.props; + const { budgetSimulator } = blocnote; + const { data } = budgetSimulator; + + if (data === null) { + mainContent = ; + } else { + const dataDic = this.processData(data); + bugetTables = tableKeys.map((tableKey) => { + return ( +
    + +
    + ); + }); + + mainContent = ( +
    + +
    + {bugetTables} +
    +
    + ); + } + + return ( +
    + + {mainContent} +
    + ); + } +} + +BudgetSimulator.propTypes = { + building: buildingDetailPropTypes, + blocnote: blocnoteProps, + loadBudgetSimulator: PropTypes.func, + buildingId: PropTypes.string, +}; + +const mapDispatchToProps = dispatch => ( + bindActionCreators({ + loadBudgetSimulator, + }, dispatch) +); + +const mapStateToProps = state => ({ + blocnote: state.blocnote, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(BudgetSimulator); diff --git "a/src/containers/Blocnote/FinancialInputs/\bIncomeStatements.js" "b/src/containers/Blocnote/FinancialInputs/\bIncomeStatements.js" new file mode 100644 index 0000000000000000000000000000000000000000..e8db590725766d50e245303c520eeace7b26ae79 --- /dev/null +++ "b/src/containers/Blocnote/FinancialInputs/\bIncomeStatements.js" @@ -0,0 +1,605 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, + Button, + Input, + InputGroup, + InputGroupAddon, + Table, +} from 'reactstrap'; +import ResultMessage from './../../../components/Blocnote/FinancialInputs/ResultMessage'; + + +class IncomeStatements extends Component { + constructor(props) { + super(props); + this.toggleGrowthRate = this.toggleGrowthRate.bind(this); + this.changeGrowthRate = this.changeGrowthRate.bind(this); + this.handleOnChange = this.handleOnChange.bind(this); + // this.handleOnChangeNew = this.handleOnChangeNew.bind(this); + + const obj = { + GRDropdownOpen: false, + GRDropdownValue: 'Select Growth Rate', + selectedDropdownId: 0, + growthRateOptions: [ + { id: '-2', key: '-2', name: 'CAGR=1.58%' }, + { id: '-1', key: '-1', name: 'Average' }, + { id: '0', key: '0', name: '0%' }, + { id: '1', key: '1', name: '1%' }, + { id: '2', key: '2', name: '2%' }, + { id: '3', key: '3', name: '3%' }, + { id: '4', key: '4', name: '4%' }, + { id: '5', key: '5', name: '5%' }, + { id: '6', key: '6', name: '6%' }, + { id: '7', key: '7', name: '7%' }, + { id: '8', key: '8', name: '8%' }, + { id: '9', key: '9', name: '9%' }, + { id: '10', key: '10', name: '10%' }, + { id: '11', key: '11', name: '11%' }, + { id: '12', key: '12', name: '12%' }, + { id: '13', key: '13', name: '13%' }, + { id: '14', key: '14', name: '14%' }, + { id: '15', key: '15', name: '15%' }, + ], + incomeStatements: props.data, + incomeStatementsNew: null, + error: false, + loading: false, + action: null, + }; + + const histYears = {}; + const incomeStatementsNew = { incomeStatementsNew: [] }; + if (props.data.hist !== null) { + Object.keys(props.data.hist).forEach((year, id) => { + const histYear = `Year-${id + 1}`; + histYears[histYear] = parseInt(year, 10); + }); + } else { + [0, 1, 2].forEach(id => { + const histYear = `Year-${id + 1}`; + histYears[histYear] = null; + }); + + const growthRate = {}; + growthRate['growth-rate'] = props.data.cagr; + incomeStatementsNew.incomeStatementsNew.push(this.initIncomeStatement()); + incomeStatementsNew.incomeStatementsNew.push(this.initIncomeStatement()); + incomeStatementsNew.incomeStatementsNew.push(this.initIncomeStatement()); + incomeStatementsNew.incomeStatementsNew.push(growthRate); + } + + this.state = Object.assign(obj, histYears, incomeStatementsNew); + console.log(this.state); // eslint-disable-line + } + + initIncomeStatement = () => { + const statement = { + year: null, + revenue: null, + }; + statement['utility-expense'] = null; + statement['non-utility-operating-expense'] = null; + return statement; + } + + handleOnChange = (event) => { + const year = event.target.id; + const targetName = event.target.name; + const copyIS = this.state.incomeStatements; + Object.entries(copyIS.hist).forEach((incomeStatement) => { + if (incomeStatement[0] === year) { + copyIS.hist[year][targetName] = parseFloat(event.target.value); + } + }); + + this.setState({ incomeStatements: copyIS }, () => { + console.log(this.state.incomeStatements); // eslint-disable-line + }); + } + + handleOnChangeNew = (event) => { + const id = event.target.id; + const targetName = event.target.name.includes('Year') ? 'year' : event.target.name; + const targetValue = event.target.value; + const copyISNew = this.state.incomeStatementsNew.map((item, key) => { + if (parseInt(key, 10) === parseInt(id, 10)) { + const tmp = item; + tmp[targetName] = targetValue; + return tmp; + } + return item; + }); + this.setState({ incomeStatementsNew: copyISNew }, () => { + console.log(this.state.incomeStatementsNew); // eslint-disable-line + }); + } + + toggleGrowthRate() { + this.setState({ + GRDropdownOpen: !this.state.GRDropdownOpen, + }); + } + + changeGrowthRate(e) { + this.setState({ GRDropdownValue: e.currentTarget.textContent }); + this.setState({ selectedDropdownId: e.currentTarget.id }); + } + + buildHeader = () => { + const headers = []; + const year = 'year'; + headers.push( + + Year + + ); + + Object.entries(this.state).forEach((entry, key) => { + if (entry[0].includes('Year')) { + headers.push( + + + + ); + } + }); + const futureYear = this.state.incomeStatements.future.year; + headers.push( + + {futureYear} + + ); + const average = 'average'; + headers.push( + + Average + + ); + return headers; + } + + buildEmptyHeader = () => { + const headers = []; + const year = 'year'; + headers.push( + + Year + + ); + let id = 0; + Object.entries(this.state).forEach((entry, key) => { + if (entry[0].includes('Year')) { + headers.push( + + + + ); + id += 1; + } + }); + headers.push( + + {' '}Next Year{' '} + + ); + headers.push( + + {' '}Average{' '} + + ); + return headers; + } + + handleUpdateIncomeStatements = () => { + this.setState({ loading: true }); + const hist = []; + Object.entries(this.state.incomeStatements.hist).forEach(incomeStatement => { + const obj = { + year: String(incomeStatement[0]), + revenue: String(incomeStatement[1].revenue), + }; + obj['non-utility-operating-expense'] = String(incomeStatement[1].non_utility_expense); + obj['utility-expense'] = String(incomeStatement[1].utility_expense); + hist.push(obj); + }); + const growthRate = {}; + growthRate['growth-rate'] = String(this.state.selectedDropdownId); + hist.push(growthRate); + this.props.updateIncomeStatements( + this.props.buildingId, + hist, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + + handleCreateIncomeStatements = () => { + const emptyMessages = []; + const incomeStatementsNew = Object.values(this.state.incomeStatementsNew.slice(0, 3)) + .map(statement => { + if (statement.year === null && + !emptyMessages.includes('Year')) { + emptyMessages.push('Year'); + } + if (statement.revenue === null && + !emptyMessages.includes('Revenue')) { + emptyMessages.push('Revenue'); + } + if (statement['utility-expense'] === null && + !emptyMessages.includes('Utility Expense')) { + emptyMessages.push('Utility Expense'); + } + if (statement['non-utility-operating-expense'] === null && + !emptyMessages.includes('Non-Utility Operating Expense')) { + emptyMessages.push('Non-Utility Operating Expense'); + } + + const newStatement = {}; + newStatement.year = String(statement.year); + newStatement.revenue = String(statement.revenue); + newStatement['utility-expense'] = String(statement['utility-expense']); + newStatement['non-utility-operating-expense'] = String(statement['non-utility-operating-expense']); + return newStatement; + }); + + if (emptyMessages.length !== 0) { + const msg = `Please fill in ${emptyMessages.join(', ')} fields.`; + alert(msg); + } else { + const growthRate = {}; + growthRate['growth-rate'] = String(this.state.incomeStatementsNew[3]['growth-rate']); + incomeStatementsNew.push(growthRate); + this.setState({ loading: true }); + this.props.updateIncomeStatements( + this.props.buildingId, + incomeStatementsNew, + ); + this.setState({ + loading: false, + action: 'updated', + }); + } + } + + render() { + let header = []; + let rows = []; + let calculateSaveButton = null; + + const columnKeys = [ + 'revenue', + 'utility_expense', + 'energy_opex', + 'electric_opex', + 'gas_opex', + 'oil_opex', + 'water_opex', + 'other_utility', + 'non_utility_expense', + 'net_non_energy_opex', + 'total_opex', + 'noi', + ]; + + const columnNames = { + revenue: 'Revenue', + utility_expense: 'Utility Expense', + energy_opex: 'Energy Expense', + electric_opex: 'Electric Bill', + gas_opex: 'Gas Bill', + oil_opex: 'Oil Bill', + water_opex: 'Water Bill', + other_utility: 'Other Utility Expense', + non_utility_expense: 'Non-Utility Operating Expense', + net_non_energy_opex: 'Net Non-Energy Expense', + total_opex: 'Total Expense', + noi: 'Net Operating Income', + }; + + const growthRateOptions = this.state.growthRateOptions.map(e => { + return ( + + {e.name} + + ); + }); + + if (this.state.incomeStatements.hist !== null && + this.state.incomeStatements.future !== null) { + header = this.buildHeader(); + const histYears = Object.keys(this.state.incomeStatements.hist).sort(); + rows = columnKeys.map((columnKey, id) => { + const cells = [ + ...[columnNames[columnKey]], + ...histYears.map((histYear) => { + let cellValue = this.state.incomeStatements.hist[histYear][columnKey]; + cellValue = Math.round(cellValue * 100) / 100; + if (['utility_expense', 'revenue', 'non_utility_expense'].includes(columnKey)) { + return ( + + + $ + + + + ); + } + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + return cellValue; + }), + ...[this.state.incomeStatements.future[columnKey]].map((amount) => { + let cellValue = amount; + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + return cellValue; + }), + ...Object.values([this.state.incomeStatements.average[columnKey]]).map((amount) => { + let cellValue = amount; + cellValue = Math.round(cellValue * 100) / 100; + cellValue = cellValue.toLocaleString(); + cellValue = `$${cellValue}`; + return cellValue; + }), + ]; + + const cellsData = Object.values(cells).map((celldata, key) => { + return ( + + + {celldata} + + + ); + }); + + return ( + + {cellsData} + + ); + }); + + calculateSaveButton = ( + + ); + } else { + header = this.buildEmptyHeader(); + const ids = Object.keys(this.state.incomeStatementsNew).slice(0, 3).sort(); + console.log(this.state.incomeStatementsNew); // eslint-disable-line + rows = columnKeys.map((columnKey, id) => { + const cells = [ + ...[columnNames[columnKey]], + ...ids.map((index) => { + const keyMapping = { + revenue: 'revenue', + utility_expense: 'utility-expense', + non_utility_expense: 'non-utility-operating-expense', + }; + + if (['revenue', 'utility_expense', 'non_utility_expense'].includes(columnKey)) { + const newColumnKey = keyMapping[columnKey]; + return ( + + + $ + + + + ); + } + return null; + }), + ...['', ''], + ]; + + const cellsData = Object.values(cells).map((celldata, key) => { + return ( + + + {celldata} + + + ); + }); + + return ( + + {cellsData} + + ); + }); + + calculateSaveButton = ( + + ); + } + + let messageStyle = {}; + let messageContent = null; + + if (this.state.loading) { + messageContent = 'Processing ...'; + messageStyle = this.props.defaultMessageStyle; + } + + if (this.state.error && typeof this.state.error === 'object') { + messageContent = this.state.error.response.statusText; + messageStyle = this.props.errorMessageStyle; + } + + if (!this.state.error && !this.state.loading + && this.state.incomeStatements !== null + && this.state.action !== null) { + messageContent = this.state.action.charAt(0).toUpperCase() + this.state.action.slice(1); + messageStyle = this.props.successMessageStyle; + } + + return ( +
    +

    + Income Statements (in $, End of Year) +

    +
    +
    + + + {this.state.GRDropdownValue} + + + {growthRateOptions} + + +
    +
    +    + {calculateSaveButton} +
    +
    +
    +
    + + + {header} + + + {rows} + +
    +
    +
    +
    + ); + } +} + +IncomeStatements.propTypes = { + blockStyle: PropTypes.shape({ + marginBottom: PropTypes.string, + }), + headerStyle: PropTypes.shape({ + textAlign: PropTypes.string, + marginBottom: PropTypes.string, + }), + buildingId: PropTypes.number, + data: PropTypes.shape({ + average: PropTypes.shape({ + electric_opex: PropTypes.number, + energy_opex: PropTypes.number, + gas_opex: PropTypes.number, + net_non_energy_opex: PropTypes.number, + noi: PropTypes.number, + non_utility_expense: PropTypes.number, + oil_opex: PropTypes.number, + other_utility: PropTypes.number, + revenue: PropTypes.number, + total_opex: PropTypes.number, + utility_expense: PropTypes.number, + water_opex: PropTypes.number, + year: null, + }), + cagr: PropTypes.number, + future: PropTypes.shape({ + electric_opex: PropTypes.number, + energy_opex: PropTypes.number, + gas_opex: PropTypes.number, + net_non_energy_opex: PropTypes.number, + noi: PropTypes.number, + non_utility_expense: PropTypes.number, + oil_opex: PropTypes.number, + other_utility: PropTypes.number, + revenue: PropTypes.number, + total_opex: PropTypes.number, + utility_expense: PropTypes.number, + water_opex: PropTypes.number, + year: null, + }), + growth_rate: PropTypes.number, + hist: PropTypes.objectOf, + result: PropTypes.arrayOf(PropTypes.shape({ + year: PropTypes.string, + revenue: PropTypes.string, + utility_expense: PropTypes.string, + non_utility_operating_expense: PropTypes.string, + })), + }), + successMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + errorMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + defaultMessageStyle: PropTypes.shape({ + color: PropTypes.string, + paddingLeft: PropTypes.string, + fontWeight: PropTypes.string, + }), + updateIncomeStatements: PropTypes.func, +}; + +export default IncomeStatements; diff --git a/src/containers/Blocnote/FinancialInputs/actions.js b/src/containers/Blocnote/FinancialInputs/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..f33d0c046c7a6530cbf94d005f60542e293fe169 --- /dev/null +++ b/src/containers/Blocnote/FinancialInputs/actions.js @@ -0,0 +1,68 @@ +import * as constants from './constants'; +import { makeActionCreator } from '../../../utils/reduxHelpers'; + +export const loadFinanceOverview = makeActionCreator(constants.FINANCE_OVERVIEW_REQUESTED, 'buildingId'); +export const financeOverviewLoaded = makeActionCreator(constants.FINANCE_OVERVIEW_SUCCEEDED, 'instance'); +export const financeOverviewFailed = makeActionCreator(constants.FINANCE_OVERVIEW_FAILED, 'error'); +export const loadBills = makeActionCreator(constants.BILLS_REQUESTED, 'buildingId'); +export const billsLoaded = makeActionCreator(constants.BILLS_SUCCEEDED, 'instance'); +export const billsFailed = makeActionCreator(constants.BILLS_FAILED, 'error'); +export const loadBillsSummary = makeActionCreator(constants.BILLS_SUMMARY_REQUESTED, 'buildingId'); +export const billsSummaryLoaded = makeActionCreator(constants.BILLS_SUMMARY_SUCCEEDED, 'instance'); +export const billsSummaryFailed = makeActionCreator(constants.BILLS_SUMMARY_FAILED, 'error'); +export const loadBillsOverview = makeActionCreator(constants.BILLS_OVERVIEW_REQUESTED, 'buildingId'); +export const billsOverviewLoaded = makeActionCreator(constants.BILLS_OVERVIEW_SUCCEEDED, 'instance'); +export const billsOverviewFailed = makeActionCreator(constants.BILLS_OVERVIEW_FAILED, 'error'); +export const loadCashBalance = makeActionCreator(constants.CASH_BALANCE_REQUESTED, 'buildingId'); +export const cashBalanceLoaded = makeActionCreator(constants.CASH_BALANCE_SUCCEEDED, 'instance'); +export const cashBalanceFailed = makeActionCreator(constants.CASH_BALANCE_FAILED, 'error'); +export const loadLoanOptions = makeActionCreator(constants.LOAN_OPTIONS_REQUESTED, 'buildingId'); +export const loanOptionsLoaded = makeActionCreator(constants.LOAN_OPTIONS_SUCCEEDED, 'instance'); +export const loanOptionsFailed = makeActionCreator(constants.LOAN_OPTIONS_FAILED, 'error'); +export const loadCustomerPreference = makeActionCreator(constants.CUSTOMER_PREFERENCE_REQUESTED, 'buildingId'); +export const customerPreferenceLoaded = makeActionCreator(constants.CUSTOMER_PREFERENCE_SUCCEEDED, 'instance'); +export const customerPreferenceFailed = makeActionCreator(constants.CUSTOMER_PREFERENCE_FAILED, 'error'); +export const loadIncomeStatement = makeActionCreator(constants.INCOME_STATEMENT_REQUESTED, 'buildingId'); +export const incomeStatementLoaded = makeActionCreator(constants.INCOME_STATEMENT_SUCCEEDED, 'instance'); +export const incomeStatementFailed = makeActionCreator(constants.INCOME_STATEMENT_FAILED, 'error'); +export const loadLiabilities = makeActionCreator(constants.LIABILITIES_REQUESTED, 'buildingId'); +export const liabilitiesLoaded = makeActionCreator(constants.LIABILITIES_SUCCEEDED, 'instance'); +export const liabilitiesFailed = makeActionCreator(constants.LIABILITIES_FAILED, 'error'); + +export const createBillsSummary = makeActionCreator(constants.CREATE_BILLS_SUMMARY_REQUESTED, 'buildingId', 'payload'); +export const createBillsSummarySucceeded = makeActionCreator(constants.CREATE_BILLS_SUMMARY_SUCCEEDED, 'instance', 'result'); +export const createBillsSummaryFailed = makeActionCreator(constants.CREATE_BILLS_SUMMARY_FAILED, 'error'); +export const updateBillsSummary = makeActionCreator(constants.UPDATE_BILLS_SUMMARY_REQUESTED, 'buildingId', 'payload'); +export const updateBillsSummarySucceeded = makeActionCreator(constants.UPDATE_BILLS_SUMMARY_SUCCEEDED, 'instance'); +export const updateBillsSummaryFailed = makeActionCreator(constants.UPDATE_BILLS_SUMMARY_FAILED, 'error'); +export const deleteBillsSummary = makeActionCreator(constants.DELETE_BILLS_SUMMARY_REQUESTED, 'buildingId', 'payload'); +export const deleteBillsSummarySucceeded = makeActionCreator(constants.DELETE_BILLS_SUMMARY_SUCCEEDED, 'instance'); +export const deleteBillsSummaryFailed = makeActionCreator(constants.DELETE_BILLS_SUMMARY_FAILED, 'error'); + +export const createBillsOverview = makeActionCreator(constants.CREATE_BILLS_OVERVIEW_REQUESTED, 'buildingId', 'payload'); +export const createBillsOverviewSucceeded = makeActionCreator(constants.CREATE_BILLS_OVERVIEW_SUCCEEDED, 'instance'); +export const createBillsOverviewFailed = makeActionCreator(constants.CREATE_BILLS_OVERVIEW_FAILED, 'error'); +export const updateBillsOverview = makeActionCreator(constants.UPDATE_BILLS_OVERVIEW_REQUESTED, 'buildingId', 'payload'); +export const updateBillsOverviewSucceeded = makeActionCreator(constants.UPDATE_BILLS_OVERVIEW_SUCCEEDED, 'instance'); +export const updateBillsOverviewFailed = makeActionCreator(constants.UPDATE_BILLS_OVERVIEW_FAILED, 'error'); + +export const updateIncomeStatements = makeActionCreator(constants.UPDATE_INCOME_STATEMENTS_REQUESTED, 'buildingId', 'payload'); +export const updateIncomeStatementsSucceeded = makeActionCreator(constants.UPDATE_INCOME_STATEMENTS_SUCCEEDED, 'instance'); +export const updateIncomeStatementsFailed = makeActionCreator(constants.UPDATE_INCOME_STATEMENTS_FAILED, 'error'); + +export const updateCashBalance = makeActionCreator(constants.UPDATE_CASH_BALANCE_REQUESTED, 'buildingId', 'payload'); +export const updateCashBalanceSucceeded = makeActionCreator(constants.UPDATE_CASH_BALANCE_SUCCEEDED, 'instance'); +export const updateCashBalanceFailed = makeActionCreator(constants.UPDATE_CASH_BALANCE_FAILED, 'error'); + +export const updateLiabilities = makeActionCreator(constants.UPDATE_LIABILITIES_REQUESTED, 'buildingId', 'payload'); +export const updateLiabilitiesSucceeded = makeActionCreator(constants.UPDATE_LIABILITIES_SUCCEEDED, 'instance'); +export const updateLiabilitiesFailed = makeActionCreator(constants.UPDATE_LIABILITIES_FAILED, 'error'); + +export const updateLoanOptions = makeActionCreator(constants.UPDATE_LOAN_OPTIONS_REQUESTED, 'buildingId', 'payload'); +export const updateLoanOptionsSucceeded = makeActionCreator(constants.UPDATE_LOAN_OPTIONS_SUCCEEDED, 'instance'); +export const updateLoanOptionsFailed = makeActionCreator(constants.UPDATE_LOAN_OPTIONS_FAILED, 'error'); + +export const updateCustomerPreference = makeActionCreator(constants.UPDATE_CUSTOMER_PREFERENCE_REQUESTED, 'buildingId', 'payload'); +export const updateCustomerPreferenceSucceeded = makeActionCreator(constants.UPDATE_CUSTOMER_PREFERENCE_SUCCEEDED, 'instance'); +export const updateCustomerPreferenceFailed = makeActionCreator(constants.UPDATE_CUSTOMER_PREFERENCE_FAILED, 'error'); + diff --git a/src/containers/Blocnote/FinancialInputs/constants.js b/src/containers/Blocnote/FinancialInputs/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..de4a0ad4f73da6db65ba8ffb30e02099e936bf31 --- /dev/null +++ b/src/containers/Blocnote/FinancialInputs/constants.js @@ -0,0 +1,64 @@ +export const FINANCE_OVERVIEW_REQUESTED = 'FINANCE_OVERIVEW_REQUESTED'; +export const FINANCE_OVERVIEW_SUCCEEDED = 'FINANCE_OVERIVEW_SUCCEEDED'; +export const FINANCE_OVERVIEW_FAILED = 'FINANCE_OVERIVEW_FAILED'; +export const BILLS_REQUESTED = 'BILLS_REQUESTED'; +export const BILLS_SUCCEEDED = 'BILLS_SUCCEEDED'; +export const BILLS_FAILED = 'BILLS_FAILED'; +export const BILLS_OVERVIEW_REQUESTED = 'BILLS_OVERVIEW_REQUESTED'; +export const BILLS_OVERVIEW_SUCCEEDED = 'BILLS_OVERVIEW_SUCCEEDED'; +export const BILLS_OVERVIEW_FAILED = 'BILLS_OVERVIEW_FAILED'; +export const BILLS_SUMMARY_REQUESTED = 'BILLS_SUMMARY_REQUESTED'; +export const BILLS_SUMMARY_SUCCEEDED = 'BILLS_SUMMARY_SUCCEEDED'; +export const BILLS_SUMMARY_FAILED = 'BILLS_SUMMARY_FAILED'; +export const CASH_BALANCE_REQUESTED = 'CASH_BALANCE_REQUESTED'; +export const CASH_BALANCE_SUCCEEDED = 'CASH_BALANCE_SUCCEEDED'; +export const CASH_BALANCE_FAILED = 'CASH_BALANCE_FAILED'; +export const CUSTOMER_PREFERENCE_REQUESTED = 'CUSTOMER_PREFERENCE_REQUESTED'; +export const CUSTOMER_PREFERENCE_SUCCEEDED = 'CUSTOMER_PREFERENCE_SUCCEEDED'; +export const CUSTOMER_PREFERENCE_FAILED = 'CUSTOMER_PREFERENCE_FAILED'; +export const INCOME_STATEMENT_REQUESTED = 'INCOME_STATEMENT_REQUESTED'; +export const INCOME_STATEMENT_SUCCEEDED = 'INCOME_STATEMENT_SUCCEEDED'; +export const INCOME_STATEMENT_FAILED = 'INCOME_STATEMENT_FAILED'; +export const LOAN_OPTIONS_REQUESTED = 'LOAN_OPTIONS_REQUESTED'; +export const LOAN_OPTIONS_SUCCEEDED = 'LOAN_OPTIONS_SUCCEEDED'; +export const LOAN_OPTIONS_FAILED = 'LOAN_OPTIONS_FAILED'; +export const LIABILITIES_REQUESTED = 'LIABILITIES_REQUESTED'; +export const LIABILITIES_SUCCEEDED = 'LIABILITIES_SUCCEEDED'; +export const LIABILITIES_FAILED = 'LIABILITIES_FAILED'; + +export const CREATE_BILLS_SUMMARY_REQUESTED = 'CREATE_BILLS_SUMMARY_REQUESTED'; +export const CREATE_BILLS_SUMMARY_SUCCEEDED = 'CREATE_BILLS_SUMMARY_SUCCEEDED'; +export const CREATE_BILLS_SUMMARY_FAILED = 'CREATE_BILLS_SUMMARY_FAILED'; +export const UPDATE_BILLS_SUMMARY_REQUESTED = 'UPDATE_BILLS_SUMMARY_REQUESTED'; +export const UPDATE_BILLS_SUMMARY_SUCCEEDED = 'UPDATE_BILLS_SUMMARY_SUCCEEDED'; +export const UPDATE_BILLS_SUMMARY_FAILED = 'UPDATE_BILLS_SUMMARY_FAILED'; +export const DELETE_BILLS_SUMMARY_REQUESTED = 'DELETE_BILLS_SUMMARY_REQUESTED'; +export const DELETE_BILLS_SUMMARY_SUCCEEDED = 'DELETE_BILLS_SUMMARY_SUCCEEDED'; +export const DELETE_BILLS_SUMMARY_FAILED = 'DELETE_BILLS_SUMMARY_FAILED'; + +export const CREATE_BILLS_OVERVIEW_REQUESTED = 'CREATE_BILLS_OVERVIEW_REQUESTED'; +export const CREATE_BILLS_OVERVIEW_SUCCEEDED = 'CREATE_BILLS_OVERVIEW_SUCCEEDED'; +export const CREATE_BILLS_OVERVIEW_FAILED = 'CREATE_BILLS_OVERVIEW_FAILED'; +export const UPDATE_BILLS_OVERVIEW_REQUESTED = 'UPDATE_BILLS_OVERVIEW_REQUESTED'; +export const UPDATE_BILLS_OVERVIEW_SUCCEEDED = 'UPDATE_BILLS_OVERVIEW_SUCCEEDED'; +export const UPDATE_BILLS_OVERVIEW_FAILED = 'UPDATE_BILLS_OVERVIEW_FAILED'; + +export const UPDATE_INCOME_STATEMENTS_REQUESTED = 'UPDATE_INCOME_STATEMENTS_REQUESTED'; +export const UPDATE_INCOME_STATEMENTS_SUCCEEDED = 'UPDATE_INCOME_STATEMENTS_SUCCEEDED'; +export const UPDATE_INCOME_STATEMENTS_FAILED = 'UPDATE_INCOME_STATEMENTS_FAILED'; + +export const UPDATE_CASH_BALANCE_REQUESTED = 'UPDATE_CASH_BALANCE_REQUESTED'; +export const UPDATE_CASH_BALANCE_SUCCEEDED = 'UPDATE_CASH_BALANCE_SUCCEEDED'; +export const UPDATE_CASH_BALANCE_FAILED = 'UPDATE_CASH_BALANCE_FAILED'; + +export const UPDATE_LIABILITIES_REQUESTED = 'UPDATE_LIABILITIES_REQUESTED'; +export const UPDATE_LIABILITIES_SUCCEEDED = 'UPDATE_LIABILITIES_SUCCEEDED'; +export const UPDATE_LIABILITIES_FAILED = 'UPDATE_LIABILITIES_FAILED'; + +export const UPDATE_LOAN_OPTIONS_REQUESTED = 'UPDATE_LOAN_OPTIONS_REQUESTED'; +export const UPDATE_LOAN_OPTIONS_SUCCEEDED = 'UPDATE_LOAN_OPTIONS_SUCCEEDED'; +export const UPDATE_LOAN_OPTIONS_FAILED = 'UPDATE_LOAN_OPTIONS_FAILED'; + +export const UPDATE_CUSTOMER_PREFERENCE_REQUESTED = 'UPDATE_CUSTOMER_PREFERENCE_REQUESTED'; +export const UPDATE_CUSTOMER_PREFERENCE_SUCCEEDED = 'UPDATE_CUSTOMER_PREFERENCE_SUCCEEDED'; +export const UPDATE_CUSTOMER_PREFERENCE_FAILED = 'UPDATE_CUSTOMER_PREFERENCE_FAILED'; diff --git a/src/containers/Blocnote/FinancialInputs/index.js b/src/containers/Blocnote/FinancialInputs/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5b526be1446b6af7de6669261f5598bc717c88a7 --- /dev/null +++ b/src/containers/Blocnote/FinancialInputs/index.js @@ -0,0 +1,357 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import LinkBarDetail from '../../../components/LinkBarDetail'; +import buildingDetailPropTypes from '../../Building/propTypes'; +import './../styles.css'; +import Loading from '../../../components/Loading'; +import { + loadFinanceOverview, loadBills, loadBillsOverview, + loadBillsSummary, loadCashBalance, loadLoanOptions, + loadCustomerPreference, loadIncomeStatement, loadLiabilities, + createBillsSummary, updateBillsSummary, deleteBillsSummary, + createBillsOverview, updateBillsOverview, updateIncomeStatements, updateCashBalance, + updateLiabilities, updateLoanOptions, updateCustomerPreference, +} from './actions'; +import blocnoteProps from './../propTypes'; +import FinanceOverview from './../../../components/Blocnote/FinancialInputs/FinanceOverview'; +import Bills from '../../../components/Blocnote/FinancialInputs/Bills'; +import BillsSummary from '../../../components/Blocnote/FinancialInputs/BillsSummary'; +import BillsOverview from '../../../components/Blocnote/FinancialInputs/BillsOverview'; +import IncomeStatements from '../../../components/Blocnote/FinancialInputs/IncomeStatements'; +import CashBalance from '../../../components/Blocnote/FinancialInputs/CashBalance'; +import MortgageLiabilities from '../../../components/Blocnote/FinancialInputs/MortgageLiabilities'; +import LoanOptions from '../../../components/Blocnote/FinancialInputs/LoanOptions'; +import CustomerPreference from '../../../components/Blocnote/FinancialInputs/CustomerPreference'; +import { blocnoteURL } from '../../../utils/restServices'; + + +class FinancialInputs extends Component { + componentDidMount() { + this.props.loadFinanceOverview(this.props.buildingId); + this.props.loadBills(this.props.buildingId); + this.props.loadBillsSummary(this.props.buildingId); + this.props.loadBillsOverview(this.props.buildingId); + this.props.loadIncomeStatement(this.props.buildingId); + this.props.loadCashBalance(this.props.buildingId); + this.props.loadLiabilities(this.props.buildingId); + this.props.loadLoanOptions(this.props.buildingId); + this.props.loadCustomerPreference(this.props.buildingId); + } + + processFinanceOverview = (data) => { + const funds = []; + data.instance.funds.forEach(fund => { + funds.push({ + fund_id: fund[0], + fund_name: fund[1], + }); + }); + + const financeData = { + fundOptions: funds, + fundDropDownId: null, + fundDropDownValue: 'No Fund', + proFormaStartDate: null, + proFormaDuration: null, + analysisDate: null, + anticipatedConstructionStartDate: null, + anticipatedCommissioningDate: null, + anticipatedConstructionPeriod: null, + }; + + if (data.instance.financing_overview_data !== undefined) { + const fData = data.instance.financing_overview_data; + data.instance.funds.forEach(fund => { + if (fund[0] === fData.fund_id) { + financeData.fundDropDownValue = fund[1]; + } + }); + financeData.fundDropDownId = fData.fund_id; + financeData.proFormaStartDate = fData.pro_forma_start_date; + financeData.proFormaDuration = fData.pro_forma_duration; + financeData.analysisDate = fData.analysis_date; + financeData.anticipatedConstructionStartDate = fData.anticipated_construction_start_date; + financeData.anticipatedCommissioningDate = fData.anticipated_commissioning_date; + financeData.anticipatedConstructionPeriod = parseInt( + fData.anticipated_construction_period, 10 + ); + } + + return financeData; + } + + render() { + let mainContent = null; + const baseURL = `${blocnoteURL}buildings/${this.props.buildingId}/financial-inputs`; + const blockStyle = { marginBottom: '40px' }; + const headerStyle = { + textAlign: 'left', + marginBottom: '25px', + paddingLeft: '10px', + }; + const successMessageStyle = { + color: 'green', + paddingLeft: '25px', + fontWeight: 'bold', + }; + const errorMessageStyle = { + color: 'red', + paddingLeft: '25px', + fontWeight: 'bold', + }; + const defaultMessageStyle = { + color: 'black', + paddingLeft: '25px', + fontWeight: 'bold', + }; + + console.log(this.props); // eslint-disable-line + const { blocnote } = this.props; + const { + fianceOverview, bills, billsOverview, billsSummary, cashBalance, + loanOptions, incomeStatement, liabilities, customerPreference, + } = blocnote; + + mainContent = ; + console.log(blocnote); // eslint-disable-line + + if (fianceOverview.data !== null && + bills.data !== null && + billsOverview.data !== null && + billsSummary.data !== null && + cashBalance.data !== null && + loanOptions.data !== null && + incomeStatement.data !== null && + liabilities.data !== null && + customerPreference.data !== null) { + console.log(fianceOverview); // eslint-disable-line + console.log(bills.data); // eslint-disable-line + console.log(billsOverview.data); // eslint-disable-line + console.log(billsSummary.data); // eslint-disable-line + console.log(cashBalance.data); // eslint-disable-line + console.log(loanOptions.data); // eslint-disable-line + console.log(incomeStatement.data); // eslint-disable-line + console.log(liabilities.data); // eslint-disable-line + console.log(customerPreference.data); // eslint-disable-line + const foData = this.processFinanceOverview(fianceOverview.data); + + const financeOverviewExist = + fianceOverview.data.instance.financing_overview_data !== undefined; + let cashBalanceData = []; + let loanOptionsData = []; + let billsSummaryData = { + gas: [], + oil: [], + water: [], + electric: [], + }; + let customerPreferenceData = {}; + if (Object.keys(cashBalance.data).length !== 0) { + cashBalanceData = cashBalance.data.instance.result; + } + if (loanOptions.data.load === true) { + loanOptionsData = loanOptions.data.status; + } + if (Object.keys(billsSummary.data.result.user_bill).length === 0) { + Object.entries(billsSummary.data.result).forEach((billSummary, id) => { + if (billSummary[0] !== 'user_bill') { + billsSummaryData[billSummary[0]] = [{ + year: String(Object.keys(billSummary[1])[0]), + charge: String(Object.values(billSummary[1])[0]), + id, + }]; + } + }); + } else { + billsSummaryData = billsSummary.data.result.user_bill; + } + if (Object.keys(customerPreference.data).length !== 0) { + customerPreferenceData = customerPreference.data.instance; + } + console.log(billsSummaryData); // eslint-disable-line + + mainContent = ( +
    + + + + + + + + + +
    + ); + } + + return ( +
    + + {mainContent} +
    + ); + } +} + +FinancialInputs.propTypes = { + buildingId: PropTypes.string, + building: buildingDetailPropTypes, + blocnote: blocnoteProps, + loadFinanceOverview: PropTypes.func, + loadBills: PropTypes.func, + loadBillsSummary: PropTypes.func, + loadBillsOverview: PropTypes.func, + loadIncomeStatement: PropTypes.func, + loadCashBalance: PropTypes.func, + loadLiabilities: PropTypes.func, + loadLoanOptions: PropTypes.func, + loadCustomerPreference: PropTypes.func, + createBillsSummary: PropTypes.func, + updateBillsSummary: PropTypes.func, + deleteBillsSummary: PropTypes.func, + createBillsOverview: PropTypes.func, + updateBillsOverview: PropTypes.func, + updateIncomeStatements: PropTypes.func, + updateCashBalance: PropTypes.func, + updateLiabilities: PropTypes.func, + updateLoanOptions: PropTypes.func, + updateCustomerPreference: PropTypes.func, +}; + +const mapDispatchToProps = dispatch => { + return bindActionCreators({ + loadFinanceOverview, + loadBills, + loadBillsSummary, + loadBillsOverview, + loadIncomeStatement, + loadCashBalance, + loadLiabilities, + loadLoanOptions, + loadCustomerPreference, + createBillsSummary, + updateBillsSummary, + deleteBillsSummary, + createBillsOverview, + updateBillsOverview, + updateIncomeStatements, + updateCashBalance, + updateLiabilities, + updateLoanOptions, + updateCustomerPreference, + }, dispatch); +}; + +const mapStateToProps = state => ({ + blocnote: state.blocnote, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(FinancialInputs); diff --git a/src/containers/Blocnote/PreliminaryFinance/actions.js b/src/containers/Blocnote/PreliminaryFinance/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..0fd3725efb0b29883329c2affad9b2654fc48433 --- /dev/null +++ b/src/containers/Blocnote/PreliminaryFinance/actions.js @@ -0,0 +1,10 @@ +import * as constants from './constants'; +import { makeActionCreator } from '../../../utils/reduxHelpers'; + +export const loadScenario = makeActionCreator(constants.SCENARIO_REQUESTED, 'buildingId'); +export const scenarioLoaded = makeActionCreator(constants.SCENARIO_SUCCEEDED, 'instance'); +export const scenarioFailed = makeActionCreator(constants.SCENARIO_FAILED, 'error'); + +export const updateScenario = makeActionCreator(constants.UPDATE_SCENARIO_REQUESTED, 'buildingId', 'payload'); +export const updateScenarioSucceeded = makeActionCreator(constants.UPDATE_SCENARIO_SUCCEEDED, 'instance'); +export const updateScenarioFailed = makeActionCreator(constants.UPDATE_SCENARIO_FAILED, 'error'); diff --git a/src/containers/Blocnote/PreliminaryFinance/constants.js b/src/containers/Blocnote/PreliminaryFinance/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..ba79846104e8659d0769dc4a94a80791d46e285f --- /dev/null +++ b/src/containers/Blocnote/PreliminaryFinance/constants.js @@ -0,0 +1,7 @@ +export const SCENARIO_REQUESTED = 'SCENARIO_REQUESTED'; +export const SCENARIO_SUCCEEDED = 'SCENARIO_SUCCEEDED'; +export const SCENARIO_FAILED = 'SCENARIO_FAILED'; + +export const UPDATE_SCENARIO_REQUESTED = 'UPDATE_SCENARIO_REQUESTED'; +export const UPDATE_SCENARIO_SUCCEEDED = 'UPDATE_SCENARIO_SUCCEEDED'; +export const UPDATE_SCENARIO_FAILED = 'UPDATE_SCENARIO_FAILED'; diff --git a/src/containers/Blocnote/PreliminaryFinance/index.js b/src/containers/Blocnote/PreliminaryFinance/index.js new file mode 100644 index 0000000000000000000000000000000000000000..720090a88ecaf3128c4a103d742c027bf31e26b5 --- /dev/null +++ b/src/containers/Blocnote/PreliminaryFinance/index.js @@ -0,0 +1,166 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import LinkBarDetail from '../../../components/LinkBarDetail'; +import './../styles.css'; +import blocnoteProps from './../propTypes'; +import Loading from '../../../components/Loading'; +import { + loadScenario, updateScenario, +} from './actions'; +import BudgetChart from './../../../components/Blocnote/PreliminaryFinance/BudgetChart'; +import LoanSummary from './../../../components/Blocnote/PreliminaryFinance/LoanSummary'; +import ProjectEconomics from './../../../components/Blocnote/PreliminaryFinance/ProjectEconomics'; +import SavingsScheduleChart from './../../../components/Blocnote/PreliminaryFinance/SavingsScheduleChart'; +import PriorRetrofitIncomeStatement from './../../../components/Blocnote/PreliminaryFinance/PriorRetrofitIncomeStatement'; +import PostRetrofitIncomeStatement from './../../../components/Blocnote/PreliminaryFinance/PostRetrofitIncomeStatement'; +import PriorRetrofitBalanceSheet from './../../../components/Blocnote/PreliminaryFinance/PriorRetrofitBalanceSheet'; +import PostRetrofitBalanceSheet from './../../../components/Blocnote/PreliminaryFinance/PostRetrofitBalanceSheet'; +import DownPayment from './../../../components/Blocnote/PreliminaryFinance/DownPayment'; +import SelectScenario from './../../../components/Blocnote/PreliminaryFinance/SelectScenario'; +import InputScenario from './../../../components/Blocnote/PreliminaryFinance/InputScenario'; +import { blocnoteURL } from '../../../utils/restServices'; + + +class PreliminaryFinance extends Component { + componentDidMount() { + this.props.loadScenario(this.props.buildingId); + } + + render() { + let mainContent = null; + const baseURL = `${blocnoteURL}buildings/${this.props.buildingId}/financial-inputs`; + const blockStyle = { marginBottom: '40px' }; + const headerStyle = { textAlign: 'left', marginBottom: '25px' }; + const successMessageStyle = { + color: 'green', + paddingLeft: '25px', + fontWeight: 'bold', + }; + const errorMessageStyle = { + color: 'red', + paddingLeft: '25px', + fontWeight: 'bold', + }; + const defaultMessageStyle = { + color: 'black', + paddingLeft: '25px', + fontWeight: 'bold', + }; + + console.log(this.props); // eslint-disable-line + const { blocnote } = this.props; + const { scenario } = blocnote; + + mainContent = ; + + if (scenario.data !== null) { + const tables = scenario.data.instance.instance.tables; + const budgets = Object.keys(tables).map(tableName => tables[tableName][0].slice(1)); + + const projectEconomicsContent = [[]]; + Object.keys(scenario.data.instance.economics_overview).forEach((key) => { + const temp = []; + temp.push(key); + temp.push(scenario.data.instance.economics_overview[key]); + projectEconomicsContent.push(temp); + }); + mainContent = ( +
    + + + + + + + + + + + +
    + ); + } + + return ( +
    + + {mainContent} +
    + ); + } +} + +PreliminaryFinance.propTypes = { + buildingId: PropTypes.string, + blocnote: blocnoteProps, + loadScenario: PropTypes.func, + updateScenario: PropTypes.func, +}; + +const mapDispatchToProps = dispatch => { + return bindActionCreators({ + loadScenario, + updateScenario, + }, dispatch); +}; + +const mapStateToProps = state => ({ + blocnote: state.blocnote, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PreliminaryFinance); diff --git a/src/containers/Blocnote/actions.js b/src/containers/Blocnote/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..2a43545fb4da65241a73d29b9e5d037b4151cabe --- /dev/null +++ b/src/containers/Blocnote/actions.js @@ -0,0 +1,6 @@ +import * as constants from './constants'; +import { makeActionCreator } from '../../utils/reduxHelpers'; + +export const loadLandingData = makeActionCreator(constants.LANDING_DATA_REQUESTED, 'buildingId'); +export const landingDataLoaded = makeActionCreator(constants.LANDING_DATA_SUCCEEDED, 'instance'); +export const landingDataFailed = makeActionCreator(constants.LANDING_DATA_FAILED, 'error'); diff --git a/src/containers/Blocnote/constants.js b/src/containers/Blocnote/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..ae65e1bd050af7321693226b0f1125e4052508e4 --- /dev/null +++ b/src/containers/Blocnote/constants.js @@ -0,0 +1,3 @@ +export const LANDING_DATA_REQUESTED = 'LANDING_DATA_REQUESTED'; +export const LANDING_DATA_SUCCEEDED = 'LANDING_DATA_SUCCEEDED'; +export const LANDING_DATA_FAILED = 'LANDING_DATA_FAILED'; diff --git a/src/containers/Blocnote/index.js b/src/containers/Blocnote/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f5d13fbdbfba352c3ee909cebec9568367940e5b --- /dev/null +++ b/src/containers/Blocnote/index.js @@ -0,0 +1,172 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { Table } from 'reactstrap'; +import { Link } from 'react-router'; +import LinkBarDetail from '../../components/LinkBarDetail'; +import buildingDetailPropTypes from '../Building/propTypes'; +import { loadLandingData } from './actions'; +import './styles.css'; +import Loading from '../../components/Loading'; +import blocnoteProps from './propTypes'; + + +class Blocnote extends Component { + componentDidMount() { + this.props.loadLandingData(this.props.building.building_id); + } + + processData = (data) => { + const rootURL = `/buildings/${this.props.building.building_id}`; + const dataDic = { + financialInputs: { + name: null, + status: null, + }, + preliminaryFinance: { + name: null, + status: null, + }, + budgetSimilator: { + name: null, + status: null, + }, + }; + + if (data.instance.if_completed) { + dataDic.name = ( + Financial Inputs + ); + dataDic.status = (Complete); + dataDic.preliminaryFinance.name = ( + Preliminary Finance + ); + dataDic.preliminaryFinance.status = (OK); + dataDic.budgetSimilator.name = ( + Budget Simulator + ); + dataDic.budgetSimilator.status = (OK); + } else { + dataDic.name = ( + Financial Inputs + ); + if (data.instance.if_started) { + dataDic.status = (Started but not complete.); + } else { + dataDic.status = (Not Started.); + } + dataDic.preliminaryFinance.name = (Preliminary Finance); + const prelimItems = data.instance.not_saved_list_for_prelim.map((item) => { + return (
  • Please fill {item}
  • ); + }); + dataDic.preliminaryFinance.status = ( +
      + {prelimItems} +
    + ); + + if (data.instance.if_completed_for_budget) { + dataDic.budgetSimilator.name = ( + Budget Simulator + ); + dataDic.budgetSimilator.status = (OK); + } else { + dataDic.budgetSimilator.name = (Budget Simulator); + const budgetItems = data.instance.not_saved_list_for_budget.map((item) => { + return (
  • Please fill {item}
  • ); + }); + dataDic.budgetSimilator.status = (
      {budgetItems}
    ); + } + } + + return dataDic; + } + + render() { + let mainContent = null; + const { blocnote } = this.props; + const { landingData } = blocnote; + const { data } = landingData; + + if (data === null) { + mainContent = ; + } else { + const dataDic = this.processData(data); + mainContent = ( +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    OPTIONSSTATUS
    + {dataDic.name} + + {dataDic.status} +
    + {dataDic.preliminaryFinance.name} + + {dataDic.preliminaryFinance.status} +
    + {dataDic.budgetSimilator.name} + + {dataDic.budgetSimilator.status} +
    +
    +
    +
    + ); + } + + return ( +
    + + {mainContent} +
    + ); + } +} + +Blocnote.propTypes = { + building: buildingDetailPropTypes, + loadLandingData: PropTypes.func, + blocnote: blocnoteProps, +}; + +const mapDispatchToProps = dispatch => ( + bindActionCreators({ + loadLandingData, + }, dispatch) +); + +const mapStateToProps = state => ({ + blocnote: state.blocnote, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Blocnote); diff --git a/src/containers/Blocnote/propTypes.js b/src/containers/Blocnote/propTypes.js new file mode 100644 index 0000000000000000000000000000000000000000..546be1a012c935b94b8ee4748af73e1c42a099e0 --- /dev/null +++ b/src/containers/Blocnote/propTypes.js @@ -0,0 +1,39 @@ +import PropTypes from 'prop-types'; + +const { shape, arrayOf, number, string, bool, oneOfType, instanceOf } = PropTypes; + +export const loadErrorPropTypes = { + loading: bool, + error: oneOfType([ + bool, + instanceOf(Error), + ]), +}; + +export const buildingProps = shape({ + building_id: number, + lot_id: number, + bbl: number, + bin: number, + street_address: string, + borough: string, + zipcode: string, + state: string, + targeting_score: number, +}); + +export const dataProps = shape({ + if_started: bool, + if_completed: bool, + if_completed_for_budget: bool, + if_started_for_budget: bool, + not_saved_list_for_prelim: arrayOf(string), + not_saved_list_for_budget: arrayOf(string), + building: buildingProps, +}); + +export default shape({ + ...loadErrorPropTypes, + data: dataProps, +}); + diff --git a/src/containers/Blocnote/reducer.js b/src/containers/Blocnote/reducer.js new file mode 100644 index 0000000000000000000000000000000000000000..28a07d32cb97899a1f95fa82cf5f79362b8484b9 --- /dev/null +++ b/src/containers/Blocnote/reducer.js @@ -0,0 +1,778 @@ +import * as LandingPage from './constants'; +import * as FinancialInputs from './FinancialInputs/constants'; +import * as PreliminaryFinance from './PreliminaryFinance/constants'; +import * as BudgetSimulator from './BudgetSimulator/constants'; + +const blocnoteInitialState = { + landingData: { + loading: false, + error: false, + data: null, + }, + fianceOverview: { + loading: false, + error: false, + data: null, + }, + billsSummary: { + loading: false, + error: false, + data: null, + }, + cashBalance: { + loading: false, + error: false, + data: null, + }, + customerPreference: { + loading: false, + error: false, + data: null, + }, + bills: { + loading: false, + error: false, + data: null, + }, + incomeStatement: { + loading: false, + error: false, + data: null, + }, + loanOptions: { + loading: false, + error: false, + data: null, + }, + liabilities: { + loading: false, + error: false, + data: null, + }, + scenario: { + loading: false, + error: false, + data: null, + }, + budgetSimulator: { + loading: false, + error: false, + data: null, + }, +}; + +export default function (state = blocnoteInitialState, action) { + console.log(state); // eslint-disable-line + switch (action.type) { + case LandingPage.LANDING_DATA_REQUESTED: + return { + ...state, + landingData: { + ...state.landingData, + loading: true, + error: false, + }, + }; + + case LandingPage.LANDING_DATA_SUCCEEDED: + return { + ...state, + landingData: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case LandingPage.LANDING_DATA_FAILED: + return { + ...state, + landingData: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.FINANCE_OVERVIEW_REQUESTED: + return { + ...state, + fianceOverview: { + ...state.fianceOverview, + loading: true, + error: false, + }, + }; + + case FinancialInputs.FINANCE_OVERVIEW_SUCCEEDED: + return { + ...state, + fianceOverview: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.FINANCE_OVERVIEW_FAILED: + return { + ...state, + fianceOverview: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.BILLS_REQUESTED: + return { + ...state, + bills: { + ...state.bills, + loading: true, + error: false, + }, + }; + + case FinancialInputs.BILLS_SUCCEEDED: + return { + ...state, + bills: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.BILLS_FAILED: + return { + ...state, + bills: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.BILLS_OVERVIEW_REQUESTED: + return { + ...state, + billsOverview: { + ...state.billsOverview, + loading: true, + error: false, + }, + }; + + case FinancialInputs.BILLS_OVERVIEW_SUCCEEDED: + return { + ...state, + billsOverview: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.BILLS_OVERVIEW_FAILED: + return { + ...state, + billsOverview: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.BILLS_SUMMARY_REQUESTED: + return { + ...state, + billsSummary: { + ...state.billsSummary, + loading: true, + error: false, + }, + }; + + case FinancialInputs.BILLS_SUMMARY_SUCCEEDED: + return { + ...state, + billsSummary: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.BILLS_SUMMARY_FAILED: + return { + ...state, + billsSummary: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.CREATE_BILLS_SUMMARY_REQUESTED: + return { + ...state, + billsSummary: { + ...state.billsSummary, + loading: true, + error: false, + }, + }; + + case FinancialInputs.CREATE_BILLS_SUMMARY_SUCCEEDED: + // const utilityType = action.instance.utility_type; + console.log(state.billsSummary.data); // eslint-disable-line + // console.log([ + // ...state.billsSummary.data, + // { + // id: action.result.id, + // utility_type: action.instance.utility_type, + // year: action.instance.year, + // charge: action.instance.charge, + // }, + // ]); // eslint-disable-line + + return { + ...state, + billsSummary: { + data: { + result: + [ + ...state.billsSummary.data, + { + id: action.result.id, + utility_type: action.instance.utility_type, + year: action.instance.year, + charge: action.instance.charge, + }, + ], + + }, + loading: false, + error: false, + }, + }; + + case FinancialInputs.CREATE_BILLS_SUMMARY_FAILED: + return { + ...state, + billsSummary: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_BILLS_SUMMARY_REQUESTED: + return { + ...state, + billsSummary: { + ...state.billsSummary, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_BILLS_SUMMARY_SUCCEEDED: + return { + ...state, + billsSummary: { + ...state.billsSummary, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_BILLS_SUMMARY_FAILED: + return { + ...state, + billsSummary: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.DELETE_BILLS_SUMMARY_REQUESTED: + return { + ...state, + billsSummary: { + ...state.billsSummary, + loading: true, + error: false, + }, + }; + + case FinancialInputs.DELETE_BILLS_SUMMARY_SUCCEEDED: + return { + ...state, + billsSummary: { + ...state.billsSummary, + loading: false, + error: false, + }, + }; + + case FinancialInputs.DELETE_BILLS_SUMMARY_FAILED: + return { + ...state, + billsSummary: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.LOAN_OPTIONS_REQUESTED: + return { + ...state, + loanOptions: { + ...state.loanOptions, + loading: true, + error: false, + }, + }; + + case FinancialInputs.LOAN_OPTIONS_SUCCEEDED: + return { + ...state, + loanOptions: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.LOAN_OPTIONS_FAILED: + console.log(action); // eslint-disable-line + return { + ...state, + loanOptions: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.CASH_BALANCE_REQUESTED: + return { + ...state, + cashBalance: { + ...state.cashBalance, + loading: true, + error: false, + }, + }; + + case FinancialInputs.CASH_BALANCE_SUCCEEDED: + return { + ...state, + cashBalance: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.CASH_BALANCE_FAILED: + return { + ...state, + cashBalance: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.INCOME_STATEMENT_REQUESTED: + return { + ...state, + incomeStatement: { + ...state.incomeStatement, + loading: true, + error: false, + }, + }; + + case FinancialInputs.INCOME_STATEMENT_SUCCEEDED: + return { + ...state, + incomeStatement: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.INCOME_STATEMENT_FAILED: + return { + ...state, + incomeStatement: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.LIABILITIES_REQUESTED: + return { + ...state, + liabilities: { + ...state.liabilities, + loading: true, + error: false, + }, + }; + + case FinancialInputs.LIABILITIES_SUCCEEDED: + return { + ...state, + liabilities: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.LIABILITIES_FAILED: + return { + ...state, + liabilities: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.CUSTOMER_PREFERENCE_REQUESTED: + return { + ...state, + customerPreference: { + ...state.customerPreference, + loading: true, + error: false, + }, + }; + + case FinancialInputs.CUSTOMER_PREFERENCE_SUCCEEDED: + return { + ...state, + customerPreference: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case FinancialInputs.CUSTOMER_PREFERENCE_FAILED: + return { + ...state, + customerPreference: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.CREATE_BILLS_OVERVIEW_REQUESTED: + return { + ...state, + billsOverview: { + ...state.billsOverview, + loading: true, + error: false, + }, + }; + + case FinancialInputs.CREATE_BILLS_OVERVIEW_SUCCEEDED: + return { + ...state, + billsOverview: { + ...state.billsOverview, + loading: false, + error: false, + }, + }; + + case FinancialInputs.CREATE_BILLS_OVERVIEW_FAILED: + return { + ...state, + billsOverview: { + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_BILLS_OVERVIEW_REQUESTED: + return { + ...state, + billsOverview: { + ...state.billsOverview, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_BILLS_OVERVIEW_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + billsOverview: { + ...state.billsOverview, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_BILLS_OVERVIEW_FAILED: + return { ...state, + billsOverview: { + ...state.billsOverview, + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_INCOME_STATEMENTS_REQUESTED: + return { + ...state, + incomeStatement: { + ...state.incomeStatement, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_INCOME_STATEMENTS_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + incomeStatement: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_INCOME_STATEMENTS_FAILED: + return { ...state, + incomeStatement: { + ...state.incomeStatement, + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_CASH_BALANCE_REQUESTED: + return { + ...state, + cashBalance: { + ...state.cashBalance, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_CASH_BALANCE_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + cashBalance: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_CASH_BALANCE_FAILED: + return { ...state, + cashBalance: { + ...state.cashBalance, + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_LIABILITIES_REQUESTED: + return { + ...state, + liabilities: { + ...state.liabilities, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_LIABILITIES_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + liabilities: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_LIABILITIES_FAILED: + return { ...state, + liabilities: { + ...state.liabilities, + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_LOAN_OPTIONS_REQUESTED: + return { + ...state, + loanOptions: { + ...state.loanOptions, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_LOAN_OPTIONS_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + loanOptions: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_LOAN_OPTIONS_FAILED: + return { ...state, + loanOptions: { + ...state.loanOptions, + loading: false, + error: action.error, + }, + }; + + case FinancialInputs.UPDATE_CUSTOMER_PREFERENCE_REQUESTED: + return { + ...state, + customerPreference: { + ...state.customerPreference, + loading: true, + error: false, + }, + }; + + case FinancialInputs.UPDATE_CUSTOMER_PREFERENCE_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + customerPreference: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case FinancialInputs.UPDATE_CUSTOMER_PREFERENCE_FAILED: + return { ...state, + customerPreference: { + ...state.customerPreference, + loading: false, + error: action.error, + }, + }; + + case PreliminaryFinance.SCENARIO_REQUESTED: + return { + ...state, + scenario: { + ...state.scenario, + loading: true, + error: false, + }, + }; + + case PreliminaryFinance.SCENARIO_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + scenario: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case PreliminaryFinance.SCENARIO_FAILED: + return { ...state, + scenario: { + ...state.scenario, + loading: false, + error: action.error, + }, + }; + + case PreliminaryFinance.UPDATE_SCENARIO_REQUESTED: + return { + ...state, + scenario: { + ...state.scenario, + loading: true, + error: false, + }, + }; + + case PreliminaryFinance.UPDATE_SCENARIO_SUCCEEDED: + console.log(action); // eslint-disable-line + console.log(state); // eslint-disable-line + return { + ...state, + scenario: { + data: { instance: action.instance }, + loading: false, + error: false, + }, + }; + + case PreliminaryFinance.UPDATE_SCENARIO_FAILED: + return { ...state, + scenario: { + ...state.scenario, + loading: false, + error: action.error, + }, + }; + + case BudgetSimulator.BUDGET_SIMULATOR_REQUESTED: + return { + ...state, + budgetSimulator: { + ...state.budgetSimulator, + loading: true, + error: false, + }, + }; + + case BudgetSimulator.BUDGET_SIMULATOR_SUCCEEDED: + return { + ...state, + budgetSimulator: { + data: action.instance, + loading: false, + error: false, + }, + }; + + case BudgetSimulator.BUDGET_SIMULATOR_FAILED: + return { + ...state, + budgetSimulator: { + loading: false, + error: action.error, + }, + }; + + default: + return state; + } +} diff --git a/src/containers/Blocnote/sagas.js b/src/containers/Blocnote/sagas.js new file mode 100644 index 0000000000000000000000000000000000000000..b07e51ad3d5efa016171da9bc95c7d9ca49c3f26 --- /dev/null +++ b/src/containers/Blocnote/sagas.js @@ -0,0 +1,361 @@ +import { call, put, takeEvery } from 'redux-saga/effects'; +import SagaRequests from '../../utils/sagaRequests'; +import request from '../../utils/request'; +import { getHeaders } from '../../utils/restServices'; +import * as LandingPage from './constants'; +import * as FinancialInputs from './FinancialInputs/constants'; +import * as PreliminaryFinance from './PreliminaryFinance/constants'; +import * as BudgetSimulator from './BudgetSimulator/constants'; + +import { + landingDataLoaded, landingDataFailed, +} from './actions'; + +import { + financeOverviewLoaded, financeOverviewFailed, + billsLoaded, billsFailed, + billsOverviewLoaded, billsOverviewFailed, + billsSummaryLoaded, billsSummaryFailed, + incomeStatementLoaded, incomeStatementFailed, + loanOptionsLoaded, loanOptionsFailed, + cashBalanceLoaded, cashBalanceFailed, + liabilitiesLoaded, liabilitiesFailed, + customerPreferenceLoaded, customerPreferenceFailed, + createBillsSummarySucceeded, createBillsSummaryFailed, + updateBillsSummarySucceeded, updateBillsSummaryFailed, + deleteBillsSummarySucceeded, deleteBillsSummaryFailed, + createBillsOverviewSucceeded, createBillsOverviewFailed, + updateBillsOverviewSucceeded, updateBillsOverviewFailed, + updateIncomeStatementsSucceeded, updateIncomeStatementsFailed, + updateCashBalanceSucceeded, updateCashBalanceFailed, + updateLiabilitiesSucceeded, updateLiabilitiesFailed, + updateLoanOptionsSucceeded, updateLoanOptionsFailed, + updateCustomerPreferenceSucceeded, updateCustomerPreferenceFailed, +} from './FinancialInputs/actions'; + +import { + scenarioLoaded, scenarioFailed, + updateScenarioSucceeded, updateScenarioFailed, +} from './PreliminaryFinance/actions'; + +import { + budgetSimulatorLoaded, budgetSimulatorFailed, +} from './BudgetSimulator/actions'; + +function* loadLandingData(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/data/`; + yield SagaRequests.get(action, url, landingDataLoaded, landingDataFailed); +} + +function* loadFinanceOverview(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/finance-overview/`; + yield SagaRequests.get(action, url, financeOverviewLoaded, financeOverviewFailed); +} + +function* loadBills(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills/`; + yield SagaRequests.get(action, url, billsLoaded, billsFailed); +} + +function* loadBillsOverview(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-overview/`; + yield SagaRequests.get(action, url, billsOverviewLoaded, billsOverviewFailed); +} + +function* loadBillsSummary(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-summary/`; + yield SagaRequests.get(action, url, billsSummaryLoaded, billsSummaryFailed); +} + +function* loadLoanOptions(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/loan-options/?loans=all-loans`; + yield SagaRequests.get(action, url, loanOptionsLoaded, loanOptionsFailed); +} + +function* loadCashBalance(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/cash-balance/`; + yield SagaRequests.get(action, url, cashBalanceLoaded, cashBalanceFailed); +} + +function* loadIncomeStatement(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/income-statement/`; + yield SagaRequests.get(action, url, incomeStatementLoaded, incomeStatementFailed); +} + +function* loadCustomerPreference(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/customer-preference/`; + yield SagaRequests.get(action, url, customerPreferenceLoaded, customerPreferenceFailed); +} + +function* loadLiabilities(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/liabilities/`; + yield SagaRequests.get(action, url, liabilitiesLoaded, liabilitiesFailed); +} + +function* loadBudgetSimulator(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/budget/budget-data/`; + yield SagaRequests.get(action, url, budgetSimulatorLoaded, budgetSimulatorFailed); + const res = yield call(request, url, { + method: 'GET', + headers: getHeaders(), + }); + + if (!res.err) { + yield put(budgetSimulatorLoaded(res)); + } else { + yield put(budgetSimulatorFailed(res.err)); + } +} + +function* createBillsSummary(action) { + console.log(action); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-summary/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'POST', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + console.log(res); // eslint-disable-line + yield put(createBillsSummarySucceeded(action.payload, res)); + } else { + yield put(createBillsSummaryFailed(res.err)); + } +} + +function* updateBillsSummary(action) { + console.log(action); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-summary/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateBillsSummarySucceeded(action.payload)); + } else { + yield put(updateBillsSummaryFailed(res.err)); + } +} + +function* deleteBillsSummary(action) { + console.log(action); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-summary/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'DELETE', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(deleteBillsSummarySucceeded(action.payload)); + } else { + yield put(deleteBillsSummaryFailed(res.err)); + } +} + +function* createBillsOverview(action) { + console.log(action); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-overview/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'POST', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + console.log(res); // eslint-disable-line + yield put(createBillsOverviewSucceeded(action.payload, res)); + } else { + yield put(createBillsOverviewFailed(res.err)); + } +} + +function* updateBillsOverview(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/bills-overview/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateBillsOverviewSucceeded(action.payload)); + } else { + yield put(updateBillsOverviewFailed(res.err)); + } +} + +function* updateIncomeStatements(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/income-statement/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateIncomeStatementsSucceeded(action.payload)); + } else { + yield put(updateIncomeStatementsFailed(res.err)); + } +} + +function* updateCashBalance(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/cash-balance/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateCashBalanceSucceeded(action.payload)); + } else { + yield put(updateCashBalanceFailed(res.err)); + } +} + +function* updateLiabilities(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/liabilities/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateLiabilitiesSucceeded(action.payload)); + } else { + yield put(updateLiabilitiesFailed(res.err)); + } +} + +function* updateLoanOptions(action) { + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/loan-options/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateLoanOptionsSucceeded(action.payload)); + } else { + yield put(updateLoanOptionsFailed(res.err)); + } +} + +function* updateCustomerPreference(action) { + console.log(action); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/financial-inputs/customer-preference/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + if (!res.err) { + yield put(updateCustomerPreferenceSucceeded(action.payload)); + } else { + yield put(updateCustomerPreferenceFailed(res.err)); + } +} + +function* loadScenario(action) { + console.log(action); // eslint-disable-line + console.log(`${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/preliminary-finance/scenario/`); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/preliminary-finance/scenario/`; + yield SagaRequests.get(action, url, scenarioLoaded, scenarioFailed); +} + +function* updateScenario(action) { + console.log(action); // eslint-disable-line + const url = `${process.env.REACT_APP_BLOCNOTE_URL}/buildings/${action.buildingId}/preliminary-finance/scenario/`; + const res = yield call( + request, + SagaRequests.generateURL(url, action), + { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(action.payload), + } + ); + + console.log(res); // eslint-disable-line + if (!res.err) { + yield put(updateScenarioSucceeded(action.payload)); + } else { + yield put(updateScenarioFailed(res.err)); + } +} + +function* blocnoteWatcher() { + yield takeEvery(LandingPage.LANDING_DATA_REQUESTED, loadLandingData); + yield takeEvery(FinancialInputs.FINANCE_OVERVIEW_REQUESTED, loadFinanceOverview); + yield takeEvery(FinancialInputs.BILLS_REQUESTED, loadBills); + yield takeEvery(FinancialInputs.BILLS_OVERVIEW_REQUESTED, loadBillsOverview); + yield takeEvery(FinancialInputs.BILLS_SUMMARY_REQUESTED, loadBillsSummary); + yield takeEvery(FinancialInputs.CASH_BALANCE_REQUESTED, loadCashBalance); + yield takeEvery(FinancialInputs.LOAN_OPTIONS_REQUESTED, loadLoanOptions); + yield takeEvery(FinancialInputs.INCOME_STATEMENT_REQUESTED, loadIncomeStatement); + yield takeEvery(FinancialInputs.CUSTOMER_PREFERENCE_REQUESTED, loadCustomerPreference); + yield takeEvery(FinancialInputs.LIABILITIES_REQUESTED, loadLiabilities); + yield takeEvery(FinancialInputs.CREATE_BILLS_SUMMARY_REQUESTED, createBillsSummary); + yield takeEvery(FinancialInputs.UPDATE_BILLS_SUMMARY_REQUESTED, updateBillsSummary); + yield takeEvery(FinancialInputs.DELETE_BILLS_SUMMARY_REQUESTED, deleteBillsSummary); + yield takeEvery(FinancialInputs.CREATE_BILLS_OVERVIEW_REQUESTED, createBillsOverview); + yield takeEvery(FinancialInputs.UPDATE_BILLS_OVERVIEW_REQUESTED, updateBillsOverview); + yield takeEvery(FinancialInputs.UPDATE_INCOME_STATEMENTS_REQUESTED, updateIncomeStatements); + yield takeEvery(FinancialInputs.UPDATE_CASH_BALANCE_REQUESTED, updateCashBalance); + yield takeEvery(FinancialInputs.UPDATE_LIABILITIES_REQUESTED, updateLiabilities); + yield takeEvery(FinancialInputs.UPDATE_LOAN_OPTIONS_REQUESTED, updateLoanOptions); + yield takeEvery(FinancialInputs.UPDATE_CUSTOMER_PREFERENCE_REQUESTED, updateCustomerPreference); + yield takeEvery(PreliminaryFinance.SCENARIO_REQUESTED, loadScenario); + yield takeEvery(PreliminaryFinance.UPDATE_SCENARIO_REQUESTED, updateScenario); + yield takeEvery(BudgetSimulator.BUDGET_SIMULATOR_REQUESTED, loadBudgetSimulator); +} + +export default blocnoteWatcher; diff --git a/src/containers/Blocnote/styles.css b/src/containers/Blocnote/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..b849a45a13feb51f31e5b5af8676647fe8be624b --- /dev/null +++ b/src/containers/Blocnote/styles.css @@ -0,0 +1,10 @@ +.leaflet-container { + height: 475px; +} + +#run-sim-btn { + position: absolute; + right: 0; + bottom: 0; + margin-bottom: 17px; +} diff --git a/src/reducers.js b/src/reducers.js index a6b710cfccb473f998eb3378f70b6372d23a1d55..5bf5ae9a1062f43b94532462678242010e5bacc5 100644 --- a/src/reducers.js +++ b/src/reducers.js @@ -16,6 +16,7 @@ import sensors from './containers/Sensors/reducer'; import buildingArea from './containers/BuildingArea/reducer'; import weather from './containers/Weather/reducer'; import events from './containers/Event/reducer'; +import blocnote from './containers/Blocnote/reducer'; export default combineReducers({ @@ -35,4 +36,5 @@ export default combineReducers({ buildingArea, weather, events, + blocnote, }); diff --git a/src/routes.js b/src/routes.js index 381b8699d32f795b53981ae64b23c82a131b7e24..9ef9c9b8d062331206808339af5334025ece1a6f 100644 --- a/src/routes.js +++ b/src/routes.js @@ -23,6 +23,10 @@ import Sensors from './containers/Sensors/Sensors'; import SensorInstall from './containers/Sensors/SensorInstall'; import GatewayList from './components/SensorInstall/GatewayList'; import BuildingReports from './containers/BuildingReports'; +import Blocnote from './containers/Blocnote'; +import FinancialInputs from './containers/Blocnote/FinancialInputs/'; +import PreliminaryFinance from './containers/Blocnote/PreliminaryFinance/'; +import BudgetSimulator from './containers/Blocnote/BudgetSimulator/'; import Wrapper from './containers/Wrapper'; import { BGroupOverview, BGroup } from './containers/BGroup'; import NavOnly from './screens/NavOnly/NavOnly'; @@ -57,6 +61,18 @@ export default ( + + + + + + + + + + + + diff --git a/src/sagas.js b/src/sagas.js index 7f33bf76b92293a53360514ddf6728f329287a39..d880c23e59846c7ff9383acd9af729ae3ff7124e 100644 --- a/src/sagas.js +++ b/src/sagas.js @@ -13,6 +13,7 @@ import sensorsSaga from './containers/Sensors/sagas'; import buildingAreaSaga from './containers/BuildingArea/sagas'; import weatherSaga from './containers/Weather/sagas'; import eventsSaga from './containers/Event/sagas'; +import blocnoteSaga from './containers/Blocnote/sagas'; export default function* rootSaga() { @@ -32,5 +33,6 @@ export default function* rootSaga() { buildingAreaSaga(), weatherSaga(), eventsSaga(), + blocnoteSaga(), ]; } diff --git a/src/utils/restServices.js b/src/utils/restServices.js index 5bc0b01049e4e9f54cc98fd9d2f22648bd911614..cc696f2996036a593dec234ade8b7d6b7848a3f2 100644 --- a/src/utils/restServices.js +++ b/src/utils/restServices.js @@ -13,6 +13,7 @@ const reportService = process.env.REACT_APP_REPORT_SERVICE; const iotService = process.env.REACT_APP_IOT_SERVICE; const userService = process.env.REACT_APP_USER_SERVICE; const weatherService = process.env.REACT_APP_WEATHER_SERVICE; +const blocnoteService = process.env.REACT_APP_BLOCNOTE_URL; export const buildingsURL = `${buildingService}/building/`; export const apartmentsURL = `${buildingService}/apartment/`; @@ -56,3 +57,5 @@ export const userURL = `${userService}/user/`; export const userGroupsURL = `${userService}/usergroup/`; export const weatherURL = `${weatherService}/weather/`; + +export const blocnoteURL = `${blocnoteService}/`;