diff --git a/package-lock.json b/package-lock.json index cf5ce13149ddf9f8388b52d03f3d91634a810856..8632471f7922cd60888c8936c07eea5d4876e2e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "@chakra-ui/react": "^2.8.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.18.0", + "recharts": "^2.9.3" }, "devDependencies": { "@types/react": "^18.2.15", @@ -2270,6 +2272,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz", + "integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", @@ -2311,6 +2321,60 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", + "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.5.tgz", + "integrity": "sha512-dfEWpZJ1Pdg8meLlICX1M3WBIpxnaH2eQV2eY43Y5ysRJOTAV9f3/R++lgJKFstfrEOE2zdJ0sv5qwr2Bkic6Q==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/json-schema": { "version": "7.0.14", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", @@ -2866,6 +2930,11 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2952,6 +3021,116 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2969,6 +3148,11 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3004,6 +3188,14 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.563", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.563.tgz", @@ -3333,12 +3525,25 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -3710,6 +3915,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -3901,6 +4114,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4305,6 +4523,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -4359,6 +4582,62 @@ } } }, + "node_modules/react-resize-detector": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", + "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-router": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", + "integrity": "sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==", + "dependencies": { + "@remix-run/router": "1.11.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0.tgz", + "integrity": "sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==", + "dependencies": { + "@remix-run/router": "1.11.0", + "react-router": "6.18.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.5.tgz", + "integrity": "sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==", + "dependencies": { + "fast-equals": "^5.0.0", + "react-transition-group": "2.9.0" + }, + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -4381,6 +4660,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dependencies": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4393,6 +4687,38 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.9.3.tgz", + "integrity": "sha512-B61sKrDlTxHvYwOCw8eYrD6rTA2a2hJg0avaY8qFI1ZYdHKvU18+J5u7sBMFg//wfJ/C5RL5+HsXt5e8tcJNLg==", + "dependencies": { + "classnames": "^2.2.5", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^8.0.4", + "react-smooth": "^2.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", @@ -4799,6 +5125,27 @@ } } }, + "node_modules/victory-vendor": { + "version": "36.6.12", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.12.tgz", + "integrity": "sha512-pJrTkNHln+D83vDCCSUf0ZfxBvIaVrFHmrBOsnnLAbdqfudRACAj51He2zU94/IWq9464oTADcPVkmWAfNMwgA==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", diff --git a/package.json b/package.json index 54151cca0f230d169e9e4d689ef9438df8bd1a3b..542dc8fff6914078972bfcaade14645f9c630dbf 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "dependencies": { "@chakra-ui/react": "^2.8.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.18.0", + "recharts": "^2.9.3" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/src/App.tsx b/src/App.tsx index f9e3eb4ed27ba3fa8140f3eb0b86e5139de5ab52..56ed4a96c1053efb9653d1c1d167cb287162e4db 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,14 @@ -import './App.css' -import Login from './Login' -// import Register from './Register' +// import './App.css' +// // import Login from './Login' +// // import Register from './Register' +// import TweetAnalytic from './component/TweetAnalytic' -function App() { - return ( - // <Register /> - <Login /> - ) -} +// function App() { +// return ( +// // <Register /> +// // <Login /> +// <TweetAnalytic /> +// ) +// } -export default App +// export default App diff --git a/src/Register.tsx b/src/Register.tsx deleted file mode 100644 index ecfd0462e7ad84674fa4ec2e23f7a6ee62bd0370..0000000000000000000000000000000000000000 --- a/src/Register.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - Box, - VStack, - Heading, -} from '@chakra-ui/layout' -import { FormControl, - FormLabel, - } from '@chakra-ui/form-control' -import { Button } from '@chakra-ui/react' -import React from 'react' -import { Input } from '@chakra-ui/react' - -function Register() { - const [name, setName] = React.useState('') - const [username, setUsername] = React.useState('') - const [password, setPassword] = React.useState('') - - const register = () => { - if (/\s/.test(username) || /\s/.test(password)) { - alert('Invalid username or password') - } - else if(name==='' || username==='' || password===''){ - alert('Please fill all the fields') - } - else if (username.length < 5 || password.length < 5) { - alert('Username and password must be at least 5 characters') - } - else { - console.log('Registering...') - console.log(name) - console.log(username) - console.log(password) - // nanti disini register ke API - } - } - - return ( - <Box - w={['full','md']} - p={[8,10]} - mx='auto' - border={['none','1px solid gray']} - mt={[20,'10hv']} - borderRadius={10} - borderColor={['','gray.200']} - > - <VStack - spacing={4} - align='flex-start' - w='full' - > - <VStack - spacing={1} - w={['full']} - align={['flex-start','center']} - > - <Heading> - Register - </Heading> - </VStack> - <FormControl> - <FormLabel> - Name - </FormLabel> - <Input variant={'filled'} value={name} onChange={(e)=>setName(e.target.value)} /> - </FormControl> - <FormControl> - <FormLabel> - Username - </FormLabel> - <Input variant={'filled'} value={username} onChange={(e)=>setUsername(e.target.value)}/> - </FormControl> - <FormControl> - <FormLabel> - Password - </FormLabel> - <Input variant={'filled'} type='password' value={password} onChange={(e)=>setPassword(e.target.value)}/> - </FormControl> - <Button colorScheme='facebook' w={'full'} onClick={register}> - Register - </Button> - </VStack> - <FormLabel> - Already have an account? <a href='#'>Login</a> - </FormLabel> - </Box> - ) -} - -export default Register \ No newline at end of file diff --git a/src/component/BoxContent.tsx b/src/component/BoxContent.tsx new file mode 100644 index 0000000000000000000000000000000000000000..aedae91725d8126fb4477ecdda6cc8d83205a508 --- /dev/null +++ b/src/component/BoxContent.tsx @@ -0,0 +1,43 @@ +import {Box,Text, Button} from '@chakra-ui/react'; +import { useLocation } from "react-router-dom" +// import React from 'react'; + +const BoxContent = ({ data }:any) => { + const location = useLocation() + const params = new URLSearchParams(location.search) + const id = params.get('id'); + let buttondetail; + if(id === null){ + buttondetail = <Button colorScheme='blue' mt='10px' onClick={() => window.location.href='/tweet-analytic?id='+data.post_id}> + Detail + </Button> + } + else{ + + } + return ( + <Box border='1px solid gray' borderRadius='10px' p='20px' mt='20px' wordBreak='break-word'> + <Box borderBottom='1px solid gray' pb='10px' mb='10px' wordBreak='break-word'> + <Text> + @{data.username} + </Text> + <Text> + {data.name} + </Text> + </Box> + <Box> + <Text> + {data.body} + </Text> + </Box> + {buttondetail} + <Text> + Post ID : {data.post_id} + </Text> + <Text> + Create At : {data.date_created} + </Text> + </Box> + ); +} +export default BoxContent; \ No newline at end of file diff --git a/src/component/Home.tsx b/src/component/Home.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7cb6173d7bb9a3d99a8e5817afe78bd9ab65271b --- /dev/null +++ b/src/component/Home.tsx @@ -0,0 +1,52 @@ +import {useState,useEffect} from "react"; +import Navbar from "./Navbar"; +import {Text,Box, Center} from "@chakra-ui/react"; +import LineChartAssetsHome from "./LineChartAssetsHome"; + + +function Home(){ + // const [username,setUsername] = useState(''); + const [name,setName] = useState(''); + const [followData,setFollowData] = useState<any[]>([]); + const nama = 'Sulthan' + useEffect(()=>{ + // setUsername(localStorage.getItem('username')!); + setName(nama); + setFollowData(FollowData); + },[]) + const FollowData = [ + { day : 'Mon' , total : 1000 }, + { day : 'Tue' , total : 3000 }, + { day : 'Wed' , total : 2000 }, + { day : 'Thu' , total : 5000 }, + { day : 'Fri' , total : 4000 }, + { day : 'Sat' , total : 3000 }, + { day : 'Sun' , total : 1000 }, + ]; + if(localStorage.getItem('username') === null){ + window.location.href = '/login'; + } + return( + <div> + <Box marginTop='80px'> + <Navbar/> + <Box> + <Box textAlign="center" paddingTop="40px" > + <Text fontSize="4xl" fontWeight="bold" fontFamily="heading" color="teal.500"> + Welcome Back {name}! + </Text> + <Text fontSize="xl" fontFamily="body" color="gray.600" marginTop="4"> + Analytic Follow + </Text> + <Center> + <LineChartAssetsHome data={followData} /> + </Center> + </Box> + </Box> + </Box> + </div> + + ) +} + +export default Home; \ No newline at end of file diff --git a/src/component/LineChartAssets.tsx b/src/component/LineChartAssets.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b0dd785234c33700e22c9cc07248edd51ec13b54 --- /dev/null +++ b/src/component/LineChartAssets.tsx @@ -0,0 +1,28 @@ +// import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend} from 'recharts'; + + +const LineChartAssets = ({ data }:any) => { + return ( + <LineChart + data={data} + width={200} + height={100} + margin={{ + top: 0, + // right: 30, + // left: -15, + bottom: 0, + }} + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis dataKey="day" /> + <YAxis /> + <Tooltip /> + <Legend /> + <Line type="monotone" dataKey="total" stroke="#8884d8" activeDot={{ r: 8 }} /> + </LineChart> + ); +} + +export default LineChartAssets; \ No newline at end of file diff --git a/src/component/LineChartAssetsHome.tsx b/src/component/LineChartAssetsHome.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d7cfff9018038c9588e991d66b1671ccd6b18a89 --- /dev/null +++ b/src/component/LineChartAssetsHome.tsx @@ -0,0 +1,28 @@ +// import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend} from 'recharts'; + + +const LineChartAssetsHome = ({ data }:any) => { + return ( + <LineChart + data={data} + width={500} + height={400} + margin={{ + top: 10, + // right: 30, + left: -15, + bottom: 0, + }} + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis dataKey="day" /> + <YAxis /> + <Tooltip /> + <Legend /> + <Line type="monotone" dataKey="total" stroke="#8884d8" activeDot={{ r: 8 }} /> + </LineChart> + ); +} + +export default LineChartAssetsHome; \ No newline at end of file diff --git a/src/Login.tsx b/src/component/Login.tsx similarity index 91% rename from src/Login.tsx rename to src/component/Login.tsx index 05e36f99f13315262426756b5be246d22f03f04b..c9494abaab9f67933815898f92f3ac98b2634e69 100644 --- a/src/Login.tsx +++ b/src/component/Login.tsx @@ -23,6 +23,8 @@ function Login() { console.log(username) console.log(password) // nanti disini login ke API + localStorage.setItem('username', username) + window.location.href = '/home' } } return ( @@ -67,7 +69,7 @@ function Login() { </Button> </VStack> <FormLabel> - Don't have an account? <a href='#'>Register</a> + Don't have an account? <a href='http://localhost:8008/login'>Register</a> </FormLabel> </Box> ) diff --git a/src/component/Navbar.tsx b/src/component/Navbar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1416ef736d291b659464c721f84715e6d54e0192 --- /dev/null +++ b/src/component/Navbar.tsx @@ -0,0 +1,43 @@ +import { Link } from 'react-router-dom'; +import { Box, Flex, Text } from '@chakra-ui/react'; +const Navbar = () => { + return ( + <Flex + as="nav" + align="center" + justify="space-between" + height='80px' + bg="teal.500" + color="white" + position="fixed" + top="0" + + width="100%" + zIndex="1000" + > + <Text fontSize="xl" fontWeight="bold" marginLeft={4}> + Analytic + </Text> + + <Box display="flex" alignItems="center"> + <Box marginRight={4}> + <Link to="/home"> + Home + </Link> + </Box > + <Box marginRight={4}> + <Link to="/tweet-analytic"> + Analytic + </Link> + </Box> + <Box marginRight={4}> + <Link to="/login" onClick={()=>{localStorage.removeItem('username')}}> + Logout + </Link> + </Box> + </Box> + </Flex> + ); +}; + +export default Navbar; \ No newline at end of file diff --git a/src/component/TweetAnalytic.tsx b/src/component/TweetAnalytic.tsx new file mode 100644 index 0000000000000000000000000000000000000000..602d160ad9bd71842ff9d2e03dd744f2e45a3918 --- /dev/null +++ b/src/component/TweetAnalytic.tsx @@ -0,0 +1,190 @@ +import {useState,useEffect} from 'react' +import LineChartAssets from './LineChartAssets'; +import {Box, Text} from '@chakra-ui/react'; +import BoxContent from './BoxContent'; +import Navbar from './Navbar'; +import { useLocation } from "react-router-dom" + +interface Data { + day: string; + total: number; +} +interface Content{ + username : string; + id : number; + name : string; + post_id : number; + body : string; + date_created : string; + path : string; +} +function TweetAnalytic(){ + if(localStorage.getItem('username') === null){ + window.location.href = '/login'; + } + const location = useLocation() + const params = new URLSearchParams(location.search) + const id = params.get('id'); + if(id === null){ + console.log('all'); + } + else{ + console.log(id); + } + const initialData = [ + { day : 'Mon' , total : 0 }, + { day : 'Tue' , total : 0 }, + { day : 'Wed' , total : 0 }, + { day : 'Thu' , total : 0 }, + { day : 'Fri' , total : 0 }, + { day : 'Sat' , total : 0 }, + { day : 'Sun' , total : 0 }, + ]; + const [replyData, setReplyData] = useState<Data[]>(initialData); + const [likeData, setLikeData] = useState<Data[]>(initialData); + const [viewData, setViewData] = useState<Data[]>(initialData); + const [content, setContent] = useState<Content[]>([]); + + //Dummy Data + const LikeData = [ + { day : 'Mon' , total : 1000 }, + { day : 'Tue' , total : 3000 }, + { day : 'Wed' , total : 2000 }, + { day : 'Thu' , total : 5000 }, + { day : 'Fri' , total : 4000 }, + { day : 'Sat' , total : 3000 }, + { day : 'Sun' , total : 1000 }, + ]; + + const ViewsData = [ + { day : 'Mon' , total : 1000 }, + { day : 'Tue' , total : 3000 }, + { day : 'Wed' , total : 2000 }, + { day : 'Thu' , total : 5000 }, + { day : 'Fri' , total : 4000 }, + { day : 'Sat' , total : 3000 }, + { day : 'Sun' , total : 1000 }, + ]; + + const RepliesData = [ + { day: 'Mon', total: 1000 }, + { day: 'Tue', total: 3000 }, + { day: 'Wed', total: 2000 }, + { day: 'Thu', total: 5000 }, + { day: 'Fri', total: 4000 }, + { day: 'Sat', total: 3000 }, + { day: 'Sun', total: 1000 }, + ]; + + const ContentData = [ + { + username: 'user1', + id: 1, + name: 'user1', + post_id: 1, + body: 'tweet1', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + { + username: 'user2', + id: 2, + name: 'user2', + post_id: 2, + body: 'tweet2', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + { + username: 'user3', + id: 3, + name: 'user3', + post_id: 3, + body: 'tweet3', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + { + username: 'user4', + id: 4, + name: 'user4', + post_id: 4, + body: 'tweet4', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + { + username: 'user5', + id: 5, + name: 'user5', + post_id: 5, + body: 'tweet5', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + { + username: 'user6', + id: 6, + name: 'user6', + post_id: 6, + body: 'tweet6', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + { + username: 'user7', + id: 7, + name: 'user7', + post_id: 7, + body: 'tweet7', + date_created: '2021-05-31', + path: 'https://pbs.twimg.com/media/E2Zu8mTUcAAGzDg?format=jpg&name=small' + }, + ]; + + + //Set Data + useEffect(() => { + setLikeData(LikeData); + setViewData(ViewsData); + setReplyData(RepliesData); + setContent(ContentData); + }, []) + + const makeContentBox = () => { + let contentBox = []; + for (let i = 0; i < content.length; i++) { + contentBox.push(<BoxContent data={content[i]} key={content[i].id} />) + } + return contentBox; + } + return ( + <div> + <Navbar /> + <Box + marginRight="240px" + marginTop="80px" + > + {makeContentBox()} + </Box> + <Box + position="fixed" + right="0" + top="78px" + height="100vh" + width="240px" + overflowX="auto" + padding="4"> + <Text fontSize="xl" fontWeight="bold">Like</Text> + <LineChartAssets data={likeData} /> + <Text fontSize="xl" fontWeight="bold">Views</Text> + <LineChartAssets data={viewData} /> + <Text fontSize="xl" fontWeight="bold">Replies</Text> + <LineChartAssets data={replyData} /> + </Box> + + </div> + ) +} + +export default TweetAnalytic; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 117a0df4894f881296e6bdf463aac310b5179c1b..82dee2ff552275945e0c9af25fe244ff8b615dd2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,13 +1,14 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import App from './App.tsx' +// import App from './App.tsx' import './index.css' import { ChakraProvider } from '@chakra-ui/react' +import Routes from './router/router' ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <ChakraProvider> - <App /> + <Routes /> </ChakraProvider> </React.StrictMode>, ) diff --git a/src/router/router.tsx b/src/router/router.tsx new file mode 100644 index 0000000000000000000000000000000000000000..040ee71b32fcb530fccf4fc1fafc4c48a60d2852 --- /dev/null +++ b/src/router/router.tsx @@ -0,0 +1,26 @@ +import { createBrowserRouter, RouterProvider } from 'react-router-dom' +import Login from '../component/Login' +import TweetAnalytic from '../component/TweetAnalytic' +import Home from '../component/Home' + +const routesList = createBrowserRouter([ + { + path: '/login', + element: <Login />, + }, + { + path: '/home', + element: <Home />, + }, + { + path: '/tweet-analytic?', + element: <TweetAnalytic />, + }, + + ]) + + const Routes = () => { + return <RouterProvider router={routesList} /> + } + + export default Routes \ No newline at end of file