From c1b3bd0808c1757e878301ba364ecd740b812f73 Mon Sep 17 00:00:00 2001 From: den-k-203 Date: Mon, 28 Oct 2024 14:07:30 +0200 Subject: [PATCH 1/6] [FE] Add filter menu --- client/package-lock.json | 595 +++++++++++++++++- client/package.json | 4 + client/src/components/DOSearch.tsx | 4 +- client/src/components/Modal/CreateModal.tsx | 4 +- .../Modal/Filter/FilterBtnComponent.tsx | 23 + .../Modal/Filter/FilterDOComponent.tsx | 16 + .../Modal/Filter/FormModalFilter.tsx | 118 ++++ .../Modal/Filter/ModelFilterMenu.tsx | 32 + client/src/components/Tables/DOTable.tsx | 2 + client/src/index.css | 35 ++ 10 files changed, 806 insertions(+), 27 deletions(-) create mode 100644 client/src/components/Modal/Filter/FilterBtnComponent.tsx create mode 100644 client/src/components/Modal/Filter/FilterDOComponent.tsx create mode 100644 client/src/components/Modal/Filter/FormModalFilter.tsx create mode 100644 client/src/components/Modal/Filter/ModelFilterMenu.tsx diff --git a/client/package-lock.json b/client/package-lock.json index d21c616..46e964b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,9 @@ "name": "client", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/material": "^6.1.5", "@reduxjs/toolkit": "^1.9.3", "axios": "^1.4.0", "faker": "^6.6.6", @@ -18,6 +21,7 @@ "react": "^18.2.0", "react-charts": "^3.0.0-beta.54", "react-dom": "^18.2.0", + "react-icons": "^5.3.0", "react-leaflet": "^4.2.0", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", @@ -184,7 +188,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, "dependencies": { "@babel/types": "^7.18.6" }, @@ -248,7 +251,6 @@ "version": "7.19.4", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -340,11 +342,12 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -389,7 +392,6 @@ "version": "7.21.3", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", @@ -399,6 +401,173 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/android-arm": { "version": "0.17.14", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.14.tgz", @@ -803,6 +972,236 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.5.tgz", + "integrity": "sha512-3J96098GrC95XsLw/TpGNMxhUOnoG9NZ/17Pfk1CrJj+4rcuolsF2RdF3XAFTu/3a/A+5ouxlSIykzYz6Ee87g==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.5.tgz", + "integrity": "sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/core-downloads-tracker": "^6.1.5", + "@mui/system": "^6.1.5", + "@mui/types": "^7.2.18", + "@mui/utils": "^6.1.5", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.1.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/@mui/material/node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/@mui/private-theming": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.5.tgz", + "integrity": "sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^6.1.5", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.5.tgz", + "integrity": "sha512-tiyWzMkHeWlOoE6AqomWvYvdml8Nv5k5T+LDwOiwHEawx8P9Lyja6ZwWPU6xljwPXYYPT2KBp1XvMly7dsK46A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.5.tgz", + "integrity": "sha512-vPM9ocQ8qquRDByTG3XF/wfYTL7IWL/20EiiKqByLDps8wOmbrDG9rVznSE3ZbcjFCFfMRMhtxvN92bwe/63SA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/private-theming": "^6.1.5", + "@mui/styled-engine": "^6.1.5", + "@mui/types": "^7.2.18", + "@mui/utils": "^6.1.5", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.18", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", + "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.5.tgz", + "integrity": "sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/types": "^7.2.18", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@npmcli/fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", @@ -858,6 +1257,16 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@react-leaflet/core": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", @@ -1030,10 +1439,17 @@ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" }, "node_modules/@types/raf": { "version": "3.4.0", @@ -1059,6 +1475,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", @@ -1202,6 +1627,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1317,6 +1757,15 @@ "node": ">=10" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -1414,6 +1863,15 @@ "node": ">=12" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1459,14 +1917,29 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1486,9 +1959,10 @@ "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" }, "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/d3-array": { "version": "3.2.4", @@ -1811,6 +2285,12 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2151,6 +2631,22 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3056,6 +3552,18 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -3102,6 +3610,15 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3329,10 +3846,20 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", + "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/react-leaflet": { "version": "4.2.1", @@ -3628,9 +4155,10 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", @@ -3661,6 +4189,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -3965,6 +4502,12 @@ "node": ">=8" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4020,7 +4563,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "engines": { "node": ">=4" } @@ -4294,6 +4836,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", diff --git a/client/package.json b/client/package.json index 33a56f4..c77cb21 100644 --- a/client/package.json +++ b/client/package.json @@ -11,6 +11,9 @@ }, "proxy": "http://localhost/api", "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/material": "^6.1.5", "@reduxjs/toolkit": "^1.9.3", "axios": "^1.4.0", "faker": "^6.6.6", @@ -21,6 +24,7 @@ "react": "^18.2.0", "react-charts": "^3.0.0-beta.54", "react-dom": "^18.2.0", + "react-icons": "^5.3.0", "react-leaflet": "^4.2.0", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", diff --git a/client/src/components/DOSearch.tsx b/client/src/components/DOSearch.tsx index 4839916..f95204d 100644 --- a/client/src/components/DOSearch.tsx +++ b/client/src/components/DOSearch.tsx @@ -21,14 +21,14 @@ const DOSearch = ({ filterMarkers, setSearch, setThisItem, select, selectOnChang }; return ( -
+
{search.length !== 0 && (
+ style={ isVisible? { position: "absolute", zIndex: 10001 } : { position: "absolute", zIndex: -1, visibility: "hidden" }}> {filterMarkers?.map((markers: any, index: number) => { return (
clickHandler(e, markers)} diff --git a/client/src/components/Modal/CreateModal.tsx b/client/src/components/Modal/CreateModal.tsx index 0c2c57b..49ea37a 100644 --- a/client/src/components/Modal/CreateModal.tsx +++ b/client/src/components/Modal/CreateModal.tsx @@ -1,7 +1,6 @@ -import React, { useState } from "react"; +import { useState } from "react"; import ModalForm from "./ModalForm"; import { useHttp } from "../../hooks/http.hook"; -import { setDestractObjects, setFilterDestractObjects } from "../../redux/slices/destractObjectSlice"; import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; const CreateModal = ({ modal }: any) => { @@ -29,7 +28,6 @@ const CreateModal = ({ modal }: any) => { // @ts-ignore delete data._id; const response = await request("http://localhost:5000/api/admin/destract-object", "POST", { ...data }, { "Authorization": `Bearer ${token}` }); - console.log(response.message); }; return (
diff --git a/client/src/components/Modal/Filter/FilterBtnComponent.tsx b/client/src/components/Modal/Filter/FilterBtnComponent.tsx new file mode 100644 index 0000000..e43ea39 --- /dev/null +++ b/client/src/components/Modal/Filter/FilterBtnComponent.tsx @@ -0,0 +1,23 @@ +import { tr } from "@faker-js/faker"; +import { FC } from "react"; +import { CiFilter } from "react-icons/ci"; + +interface FilterDOBtnComponentProps{ + active: boolean | undefined + setActive(value: boolean): void +} + +const FilterDOBtnComponent: FC = ({active, setActive}) => { + + const handleChangeActive = () => setActive(true) + + return ( +
+ +
+ ) +} + +export default FilterDOBtnComponent \ No newline at end of file diff --git a/client/src/components/Modal/Filter/FilterDOComponent.tsx b/client/src/components/Modal/Filter/FilterDOComponent.tsx new file mode 100644 index 0000000..882fc2d --- /dev/null +++ b/client/src/components/Modal/Filter/FilterDOComponent.tsx @@ -0,0 +1,16 @@ +import { FC, useState } from "react"; +import FilterDOBtnComponent from "./FilterBtnComponent"; +import ModalFilterMenuComponent from "./ModelFilterMenu"; + +const FilterDOComponent: FC = () => { + const [active, setActive] = useState(false) + + return ( + <> + + + + ) +} + +export default FilterDOComponent \ No newline at end of file diff --git a/client/src/components/Modal/Filter/FormModalFilter.tsx b/client/src/components/Modal/Filter/FormModalFilter.tsx new file mode 100644 index 0000000..0c9b881 --- /dev/null +++ b/client/src/components/Modal/Filter/FormModalFilter.tsx @@ -0,0 +1,118 @@ +import { FC, useState } from "react"; +import { TextField, Typography, FormControl, Select, InputLabel, MenuItem, Box, Button } from '@mui/material'; + +const FormModalFilter: FC = () => { + + const [dateRange, setDateRange] = useState<{ start: string; end: string }>({ start: '', end: '' }); + const [objectType, setObjectType] = useState(''); + const [damageState, setDamageState] = useState(''); + const [destroyedBy, setDestroyedBy] = useState(''); + const [victimCount, setVictimCount] = useState<{ min: number; max: number }>({ min: 0, max: 0 }); + const [district, setDistrict] = useState(''); + const [description, setDescription] = useState(''); // Додане поле для опису + + return( + <> + Фільтр меню + + setDateRange({ ...dateRange, start: e.target.value })} + fullWidth + sx={{ mb: 2, input: { color: 'transparent' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + setDateRange({ ...dateRange, end: e.target.value })} + fullWidth + sx={{ mb: 2, input: { color: 'transparent' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + + + Тип об'єкта + + + + + Стан руйнації + + + + + Чим знищено + + + + setVictimCount({ ...victimCount, min: Number(e.target.value) })} + fullWidth + sx={{ mb: 2, input: { color: 'transparent' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + setVictimCount({ ...victimCount, max: Number(e.target.value) })} + fullWidth + sx={{ mb: 2, input: { color: 'transparent' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + + setDistrict(e.target.value)} + fullWidth + sx={{ mb: 2, input: { color: 'transparent' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + + + setDescription(e.target.value)} + fullWidth + sx={{ mb: 2, input: { color: 'transparent' }, textarea: { color: 'transparent' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + + ) +} + +export default FormModalFilter \ No newline at end of file diff --git a/client/src/components/Modal/Filter/ModelFilterMenu.tsx b/client/src/components/Modal/Filter/ModelFilterMenu.tsx new file mode 100644 index 0000000..454cbcc --- /dev/null +++ b/client/src/components/Modal/Filter/ModelFilterMenu.tsx @@ -0,0 +1,32 @@ +import { FC } from "react" +import { Modal, Box, Button, Typography } from '@mui/material'; +import FormModalFilter from "./FormModalFilter"; + +interface ModalProps { + active: boolean; + setActive: (value: boolean) => void; +} + +const ModalFilterMenuComponent: FC = ({ active, setActive }) => { + + const handleClose = () => setActive(false) + + return ( + + + +
+ + +
+
+ +
+ ) +} + +export default ModalFilterMenuComponent \ No newline at end of file diff --git a/client/src/components/Tables/DOTable.tsx b/client/src/components/Tables/DOTable.tsx index 8b0edee..0e54e93 100644 --- a/client/src/components/Tables/DOTable.tsx +++ b/client/src/components/Tables/DOTable.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from "react"; import M from "materialize-css"; import Modal from "../Modal/Modal"; import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; +import FilterDOComponent from "../Modal/Filter/FilterDOComponent"; const DOTable = ({loading, isActivate }: any) => { const destractObjects = useAppSelector(state => state.destractObject.filterDoList); @@ -9,6 +10,7 @@ const DOTable = ({loading, isActivate }: any) => { return (
+ diff --git a/client/src/index.css b/client/src/index.css index d7fc569..d2e1b83 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -159,4 +159,39 @@ tab { -webkit-transform-origin: 50% 0%; transform-origin: 50% 0%; visibility: hidden +} + +.btn-filter-do{ + background-color: #8e24aa; + color: white; + border-radius: 5px; + font-size: 15px; + padding: 15px; + border-color: #8e24aa; + margin: 20px; +} + +.filter-box{ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 500px; + background-color: rgb(31, 31, 31); + border: 2px solid #000; + box-shadow: 24; + padding: 16px; + color: white; +} + +.validate{ + z-index: -1; +} + +.select-dropdown{ + z-index: -1; +} + +.input-field col s4{ + z-index: -1; } \ No newline at end of file From 3b99b915d0e2e2b7cc5b36536f35644aa82827a3 Mon Sep 17 00:00:00 2001 From: den-k-203 Date: Wed, 30 Oct 2024 17:10:21 +0200 Subject: [PATCH 2/6] [FE] Mega refactor and add filter for table with data --- client/package-lock.json | 76 ++++ client/package.json | 3 + client/public/none_image.jpg | Bin 0 -> 7946 bytes client/src/App.tsx | 1 + client/src/components/DOSearch.tsx | 6 +- client/src/components/Modal/CreateModal.tsx | 94 ++-- .../src/components/Modal/Edit/EditModal.tsx | 156 +++++++ .../Modal/Filter/FilterBtnComponent.tsx | 27 +- .../Modal/Filter/FilterDOComponent.tsx | 2 +- .../Modal/Filter/FormModalFilter.tsx | 118 ----- .../Modal/Filter/ModalFilterMenu.tsx | 144 ++++++ .../Modal/Filter/ModelFilterMenu.tsx | 32 -- client/src/components/Modal/Modal.tsx | 85 ---- client/src/components/Modal/ModalForm.tsx | 418 ++++++++++++++---- client/src/components/NavBar.tsx | 13 + client/src/components/Tables/DOTable.tsx | 83 ++-- client/src/components/Tables/EmptyTable.tsx | 41 -- client/src/components/Tables/UsersTable.tsx | 7 +- client/src/components/UsersSearch.tsx | 12 +- client/src/config/variables.ts | 72 +++ client/src/hooks/auth.hook.ts | 1 - client/src/http/DOhttp.ts | 48 ++ client/src/http/UserHttp.ts | 16 + client/src/index.css | 59 ++- client/src/pages/AdminDOPage.tsx | 26 +- client/src/pages/AdminUserPage.tsx | 13 +- client/src/pages/AnaliticsPage.tsx | 18 +- client/src/pages/MapPage.tsx | 5 +- .../src/redux/slices/destractObjectSlice.ts | 23 +- client/src/services/filter/filterDO.ts | 21 + client/src/store/DOStore.ts | 38 ++ client/src/types/ObjectDestroy.ts | 20 + client/src/types/filter.ts | 8 + 33 files changed, 1196 insertions(+), 490 deletions(-) create mode 100644 client/public/none_image.jpg create mode 100644 client/src/components/Modal/Edit/EditModal.tsx delete mode 100644 client/src/components/Modal/Filter/FormModalFilter.tsx create mode 100644 client/src/components/Modal/Filter/ModalFilterMenu.tsx delete mode 100644 client/src/components/Modal/Filter/ModelFilterMenu.tsx delete mode 100644 client/src/components/Modal/Modal.tsx delete mode 100644 client/src/components/Tables/EmptyTable.tsx create mode 100644 client/src/config/variables.ts create mode 100644 client/src/http/DOhttp.ts create mode 100644 client/src/http/UserHttp.ts create mode 100644 client/src/services/filter/filterDO.ts create mode 100644 client/src/store/DOStore.ts create mode 100644 client/src/types/ObjectDestroy.ts create mode 100644 client/src/types/filter.ts diff --git a/client/package-lock.json b/client/package-lock.json index 46e964b..3b29b39 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,8 @@ "file-saver": "^2.0.5", "leaflet": "^1.9.3", "materialize-css": "^1.0.0-rc.2", + "mobx": "^6.13.5", + "mobx-react": "^9.1.1", "node-sass": "^8.0.0", "react": "^18.2.0", "react-charts": "^3.0.0-beta.54", @@ -25,6 +27,7 @@ "react-leaflet": "^4.2.0", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", + "react-toastify": "^10.0.6", "recharts": "^2.6.2" }, "devDependencies": { @@ -3077,6 +3080,66 @@ "node": ">=10" } }, + "node_modules/mobx": { + "version": "6.13.5", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.5.tgz", + "integrity": "sha512-/HTWzW2s8J1Gqt+WmUj5Y0mddZk+LInejADc79NJadrWla3rHzmRHki/mnEUH1AvOmbNTZ1BRbKxr8DSgfdjMA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.1.1.tgz", + "integrity": "sha512-gVV7AdSrAAxqXOJ2bAbGa5TkPqvITSzaPiiEkzpW4rRsMhSec7C2NBCJYILADHKp2tzOAIETGRsIY0UaCV5aEw==", + "license": "MIT", + "dependencies": { + "mobx-react-lite": "^4.0.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/mobx-react-lite": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.7.tgz", + "integrity": "sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3982,6 +4045,19 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", + "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-transition-group": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", diff --git a/client/package.json b/client/package.json index c77cb21..772da91 100644 --- a/client/package.json +++ b/client/package.json @@ -20,6 +20,8 @@ "file-saver": "^2.0.5", "leaflet": "^1.9.3", "materialize-css": "^1.0.0-rc.2", + "mobx": "^6.13.5", + "mobx-react": "^9.1.1", "node-sass": "^8.0.0", "react": "^18.2.0", "react-charts": "^3.0.0-beta.54", @@ -28,6 +30,7 @@ "react-leaflet": "^4.2.0", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", + "react-toastify": "^10.0.6", "recharts": "^2.6.2" }, "devDependencies": { diff --git a/client/public/none_image.jpg b/client/public/none_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ed95fb0779aeb7fdf205aa588f4b88ce916ccbe GIT binary patch literal 7946 zcmeHK2UJt(wmzWq8{W{kL$pEZdb`}6& z;oF{XqPE5++vb|j-y2ha>}gjbndt3G^pa45$^*xKG%#Xg+k*0lfzSY}=pj8ZCY}Xg zWA0+tWmhuUOAQ9|^oC-biB32umgoWV$9TaMpz<(4XV@;mpRblAMIB?OaALPBHnRLu{rzk-@bo5QJh3j&nNF~;Nx2!z|NZg(Cf}s-ce%dH^-T(V6Zm^}eV6N- z6!<3a_w4#E*EcEfP2j&#cKzN?;5?Zc4n)=`kNit1D+qqk$Wh5&|dy-*QsK~TDnw95M=yvXPdpVp}r>-QGe!2f%A+ezWXh8vsM^{Vkn_W?$0qQ4U`_$C$LQjJ30TaQVY5mi3{9 zb8|E(ac4=MP8`m}1VpET(W7T<8AIZE(5i#emBywkwG;gxME?_pcH9=}j9Y2@W3LD1 zR&9_x0R=_Zc5@xeGG6oWuI#?Ov?usU%I$|1zcH-(THWxDyOKpXs9uTG@Ql^d6CccP zTqvS11vbnh^d`6qLn^ZOPltN=SF03uMKvxv67v(HuD0stkur2U3UVrvU9?AZ=Mp@rZlgT{7fYr?NC_@qh=|+F&s626D?`Nl@74*E@I&b~X*^u6 zajITPSrvv;TGHDx3mc!u>O4Z&pRa3y7gDcCN8KzKv`MWHBrwA0>pMzds%oqC9a&@K z+_>UWs!l9E)3+cn{sq-%bGc)2R{KZTl4N~%$@)}w@osZ0aXNFh#0@zSEdcQ~`s=&vw_b@VUHB9d$Vh8D#++8IhLrbi0GNi+<+R0S7JzzT@D&bdUEBvd*x{n^9gLzUWdNETE;@8>bTkEIPo*a{_hV&!YU#vCAO~r`_f_)dY~##L*6l3xp5m8 z(#4mm9)dOeAO+LsKuf7ee+n1MoV0M0Tm@Kc6Su=s>`HSu-W_-QH>O^;X!1+O}wbL}gjoRmzkIk&($NtNAE-x=&ri@%;XP6H@tEM~>!_ z#Dz{0_2@pgt-Nd-^9V67O_nke89QDS?~A2}ANqJx?%BbfEOdWAtuH6+m8noy*stz9 z;v!B*J^5-Mi0S#>l)7{Ka_dw2bh)u^HoD+iiW@;l{vN7H1bZ~@Ful?;i;la<;ULrf zv;%Bzt}UBdyFXzLF0kLjH)ka_m+Aoxm{E_@zuvnLbG+I1BS^P2qH))=Iz-F0LKVA1 z_O?W7Le#JReJ{9Mq}-s1NBQJt)RyZgU*?7 zx5Ke9$6Cnmls+c~Q-sYj72Gb46Q1@;3W@DPz)P z{ydWQB3L9?DkY=r_)^BxgC^I$XMUKzKOgCiRzW5e&!u5&E2$iqOzo7k-D7Pkq=2feN%$1yB?0Wm_wKtSXzR)OCnYjfOieAys!^0|0yah;hr! zPlckI<}o>gk)QOdalw>P*rVS!{+CtFk~h_zkTxfnLmF-~%`_s|B3qgrRI~~ikr$gO z)OpDOsAyvgd(rgP^*5xS zvsFGUJzF){*KKP^dSGtnde(i8rl8`ABT98`m3@xy7x(`%IiJjm3fdOwa+SRcywf6) zan&KBQ|IPHT?d$1V+0{#C+?^t^+IA=zBh6Cg(+?NRnDC;6h>$U*i1YK6b7|c61F`D zr;PVd_s42`R+y>AG&^J;rYt3?)dM?^2e5_Sf8k^jw|rgC-}`lG&^KPaTB{fu)~bxkP}uUI?0wCm1f@5}7$t z5|KWf%jJuN(eXF7d<_2UQsQmOQ{RH3?5hdm>-)kZZ!U z_91%z<8-S+v67xosWhj8)OwSXxD)XBlH;2GO|;Cl=H2J{dvs6FqOPhRjDMxv`0s+s zcCN$$s};9QuKY?8z0OXlEk=D$si;Zo|^#H5hGTwe zUKb5Wg1&DTF|#UjDMd`wJ)2u;$zG0u8<$K#z5b};g~o5$kMs|vg39InYZZ3KLtJym zq49NOXlx=!^{9l&2O-PseFZs;1?qAbc(6li^iyw{!N-F}PMXoL%5S8B{;y2xg1sto z5A2S-=T{*#3s|(4TH|nH*YX0bEG*v5r5DR4)X!qw%S+!7WU|u>yfwp$cV>{?o+Z^y z@tVh+H>dnm*WvKn+VTLjBYsvMz6$Y>k}0FsrbHXQPsD8p3pg3*?agi8qx^<)YKL|3 zWyHWkU*)P9#kosc09Knot=W-?a8xPo z-_(p*%AzdnO?#kWVT@V6;pOL}+!cmHk+Vs^3Hir2iH@k=hEBucR9_HJ*uQg>B__YV zR9;tQ{RUA9@(T^RSLGeR@?qaHHfVT`{q>=lhX2g+tGx(idwH&j;f|r1#>&Wh?8cD# zMRx-$ey^7#YOvNo^yED|`e^FoC(Qryt%~7^c}KeDs>ZTTZ;Qs}U6RF<+pvrXDDLel zwxZ=zGMz|O`mbu=W}`D87Y@^gBBxI+WnvSg#PYrE5|5$~rsk~|PJCX?k^l3@`IiDt z=v_$HZ1Oyn1-KsUhP1SKd>PrjA za`1U%N|B)E!zRBV9~DAgZXQh;!Z5IEU4TF!^I2hffeBq|MDqJ{Yr4a}dy-zMCtu_( zOZho#(Rf6>&hzYtT!fu)}D;faP&0};r&+Vz8G&I{a4`VOn? jd_#pdEA|t{lcQ6fjq0@UQW)3LLM1M3hvIMYwbA_#jQ2oy literal 0 HcmV?d00001 diff --git a/client/src/App.tsx b/client/src/App.tsx index e623c8e..f3de3c0 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,6 +5,7 @@ import { AuthContext } from "./context/authContext"; import { useAuth } from "./hooks/auth.hook"; import useAppSelector from "./hooks/reduxHooks/useAppSelector.hook"; import { User } from "./types/main"; +import 'react-toastify/dist/ReactToastify.css'; function App(): JSX.Element { const user: User = useAppSelector(state => state.user); diff --git a/client/src/components/DOSearch.tsx b/client/src/components/DOSearch.tsx index f95204d..d7fde57 100644 --- a/client/src/components/DOSearch.tsx +++ b/client/src/components/DOSearch.tsx @@ -21,14 +21,14 @@ const DOSearch = ({ filterMarkers, setSearch, setThisItem, select, selectOnChang }; return ( -
+
{search.length !== 0 && (
+ style={ isVisible? { position: "absolute"} : { position: "absolute", visibility: "hidden" }}> {filterMarkers?.map((markers: any, index: number) => { return (
clickHandler(e, markers)} @@ -42,7 +42,7 @@ const DOSearch = ({ filterMarkers, setSearch, setThisItem, select, selectOnChang
- diff --git a/client/src/components/Modal/CreateModal.tsx b/client/src/components/Modal/CreateModal.tsx index 49ea37a..e03ca02 100644 --- a/client/src/components/Modal/CreateModal.tsx +++ b/client/src/components/Modal/CreateModal.tsx @@ -1,45 +1,87 @@ import { useState } from "react"; import ModalForm from "./ModalForm"; -import { useHttp } from "../../hooks/http.hook"; import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; +import DestructedObjectData from "../../types/ObjectDestroy"; +import DOHttp from "../../http/DOhttp"; +import doStore from "../../store/DOStore"; +import { toast } from "react-toastify"; const CreateModal = ({ modal }: any) => { - const initialData = { - _id: "", - title: "", - position: ["", ""], - postName: "01001", - address: "Вулиця Велика Васильківська", - type: "", - area: "", - imgPath: "../../public/house.jpeg", - text: "", - percentageOfDestruction: "", - dateOfDestruction: "", - dateOfRecovery: "", - location: "", - neighborhood: "" + const initialData: DestructedObjectData = { + _id: undefined, + title: "", + position: ["", ""], + postName: "01001", + address: "", + imgPath: "../../public/none_image.jpg", + typeInfrastructure: "", + description: "", + percentageOfDestruction: "", + dateOfDestruction: "", + dateOfRecovery: "", + neighborhood: "", + typeDestruction: "", + countVictims: 0, + whatDestroyed: "", + areaName: "", + area: 0, + stateDestruction: "" }; + const [data, setData] = useState(initialData); const token = useAppSelector(state => state.token.accessToken); - const { loading, request, error, clearError } = useHttp(); + const createDOHandler = async () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete data._id; - const response = await request("http://localhost:5000/api/admin/destract-object", "POST", { ...data }, { "Authorization": `Bearer ${token}` }); + if(token){ + const response = await DOHttp.createDO(token, data) + if(response.data && response.status == 200){ + doStore.addDestructionObject(response.data) + + toast.success("Об'єкт успішно створено!", { + position: "bottom-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + + setData({ + _id: undefined, + title: "", + position: ["", ""], + postName: "01001", + address: "", + imgPath: "../../public/none_image.jpg", + typeInfrastructure: "", + description: "", + percentageOfDestruction: "", + dateOfDestruction: "", + dateOfRecovery: "", + neighborhood: "", + typeDestruction: "", + countVictims: 0, + whatDestroyed: "", + areaName: "", + area: 0, + stateDestruction: "" + }) + } + + } }; + return ( -
+

Створити запис про об'єкт руйнації

-
-
- - +
diff --git a/client/src/components/Modal/Edit/EditModal.tsx b/client/src/components/Modal/Edit/EditModal.tsx new file mode 100644 index 0000000..5022b17 --- /dev/null +++ b/client/src/components/Modal/Edit/EditModal.tsx @@ -0,0 +1,156 @@ +import { Box, Button, Grid, Modal, Typography } from "@mui/material" +import { FC, useState } from "react" +import DestructionObject from "../../../types/ObjectDestroy" +import doStore from "../../../store/DOStore" +import useAppSelector from "../../../hooks/reduxHooks/useAppSelector.hook" +import DOHttp from "../../../http/DOhttp" +import ModalForm from "../ModalForm" +import InfoItem from "../../Tables/InfoItem" +import { toast } from "react-toastify" + +interface EditModalProp{ + item: DestructionObject +} + +const EditModalComponent: FC = ({item}) => { + const initialData: DestructionObject = { + title: item ? item.title : "", + position: item ? item.position : ["0", "0"], + postName: item ? item.postName : "", + address: item ? item.address : "", + area: item ? item.area : 0, + typeInfrastructure: item ? item.typeInfrastructure : "", + imgPath: item ? item.imgPath : "", + description: item ? item.description : "", + percentageOfDestruction: item ? item.percentageOfDestruction : "", + dateOfDestruction: item ? item.dateOfDestruction : "", + dateOfRecovery: item ? item.dateOfRecovery : "", + typeDestruction: item ? item.typeDestruction : "", + countVictims: item ? item.countVictims : 0, + whatDestroyed: item ? item.whatDestroyed : "", + areaName: item ? item.areaName : "", + _id: item ? item._id : "", + neighborhood: item ? item.neighborhood : "", + stateDestruction: "" + }; + + const [data, setData] = useState(initialData); + const token = useAppSelector(state => state.token.accessToken); + + const updateHandler = async () => { + if(token && initialData._id){ + const reseponse = await DOHttp.editDO(data, token) + if(reseponse.data && reseponse.status == 200){ + doStore.updateDestructionObject(reseponse.data) + toast.success("Об'єкт успішно оновлено!", { + position: "bottom-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + } + } + }; + + const deleteHandler = async () => { + if(token && initialData._id){ + const reseponse = await DOHttp.removeDO(initialData._id, token) + if(reseponse && reseponse.status == 200){ + doStore.removeDestructionObject(initialData._id) + toast.success("Об'єкт успішно видалено!", { + position: "bottom-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + } + } + }; + + const itemData = ["Адреса", "Площа", "Тип іфраструктури", "Опис", "Зруйновано", "Дата руйнації", "Дата відновлення", "Координати", + "Тип інфраструктури", "Стан руйнації", "Кількість жертв", "Тип знищення", "Чим знищено", "Район"]; + + const [active, setActive] = useState(false) + const handleClose = () => setActive(false) + const handleOpen = () => setActive(true) + + return ( + <> + + + + + {item.title} + + + + + Картинка відсутня. + + + + + + + + + + + + + + + + Редагування даних + + + + + + + + + + + + + ) +} + +export default EditModalComponent \ No newline at end of file diff --git a/client/src/components/Modal/Filter/FilterBtnComponent.tsx b/client/src/components/Modal/Filter/FilterBtnComponent.tsx index e43ea39..145da5e 100644 --- a/client/src/components/Modal/Filter/FilterBtnComponent.tsx +++ b/client/src/components/Modal/Filter/FilterBtnComponent.tsx @@ -1,6 +1,9 @@ -import { tr } from "@faker-js/faker"; import { FC } from "react"; import { CiFilter } from "react-icons/ci"; +import { TiRefresh } from "react-icons/ti"; +import DOHttp from "../../../http/DOhttp"; +import doStore from "../../../store/DOStore"; +import { toast } from "react-toastify"; interface FilterDOBtnComponentProps{ active: boolean | undefined @@ -11,10 +14,30 @@ const FilterDOBtnComponent: FC = ({active, setActive} const handleChangeActive = () => setActive(true) + const handleRefresh = async () => { + const response = await DOHttp.getDOAll() + if(response.data && response.status == 200){ + doStore.init(response.data) + toast.success("Таблиця успішно оновлена!", { + position: "bottom-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + } + } + return (
+
) diff --git a/client/src/components/Modal/Filter/FilterDOComponent.tsx b/client/src/components/Modal/Filter/FilterDOComponent.tsx index 882fc2d..bbcc638 100644 --- a/client/src/components/Modal/Filter/FilterDOComponent.tsx +++ b/client/src/components/Modal/Filter/FilterDOComponent.tsx @@ -1,6 +1,6 @@ import { FC, useState } from "react"; import FilterDOBtnComponent from "./FilterBtnComponent"; -import ModalFilterMenuComponent from "./ModelFilterMenu"; +import ModalFilterMenuComponent from "./ModalFilterMenu"; const FilterDOComponent: FC = () => { const [active, setActive] = useState(false) diff --git a/client/src/components/Modal/Filter/FormModalFilter.tsx b/client/src/components/Modal/Filter/FormModalFilter.tsx deleted file mode 100644 index 0c9b881..0000000 --- a/client/src/components/Modal/Filter/FormModalFilter.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { FC, useState } from "react"; -import { TextField, Typography, FormControl, Select, InputLabel, MenuItem, Box, Button } from '@mui/material'; - -const FormModalFilter: FC = () => { - - const [dateRange, setDateRange] = useState<{ start: string; end: string }>({ start: '', end: '' }); - const [objectType, setObjectType] = useState(''); - const [damageState, setDamageState] = useState(''); - const [destroyedBy, setDestroyedBy] = useState(''); - const [victimCount, setVictimCount] = useState<{ min: number; max: number }>({ min: 0, max: 0 }); - const [district, setDistrict] = useState(''); - const [description, setDescription] = useState(''); // Додане поле для опису - - return( - <> - Фільтр меню - - setDateRange({ ...dateRange, start: e.target.value })} - fullWidth - sx={{ mb: 2, input: { color: 'transparent' } }} - InputLabelProps={{ style: { color: 'white' } }} - /> - setDateRange({ ...dateRange, end: e.target.value })} - fullWidth - sx={{ mb: 2, input: { color: 'transparent' } }} - InputLabelProps={{ style: { color: 'white' } }} - /> - - - Тип об'єкта - - - - - Стан руйнації - - - - - Чим знищено - - - - setVictimCount({ ...victimCount, min: Number(e.target.value) })} - fullWidth - sx={{ mb: 2, input: { color: 'transparent' } }} - InputLabelProps={{ style: { color: 'white' } }} - /> - setVictimCount({ ...victimCount, max: Number(e.target.value) })} - fullWidth - sx={{ mb: 2, input: { color: 'transparent' } }} - InputLabelProps={{ style: { color: 'white' } }} - /> - - setDistrict(e.target.value)} - fullWidth - sx={{ mb: 2, input: { color: 'transparent' } }} - InputLabelProps={{ style: { color: 'white' } }} - /> - - - setDescription(e.target.value)} - fullWidth - sx={{ mb: 2, input: { color: 'transparent' }, textarea: { color: 'transparent' } }} - InputLabelProps={{ style: { color: 'white' } }} - /> - - ) -} - -export default FormModalFilter \ No newline at end of file diff --git a/client/src/components/Modal/Filter/ModalFilterMenu.tsx b/client/src/components/Modal/Filter/ModalFilterMenu.tsx new file mode 100644 index 0000000..c96ce97 --- /dev/null +++ b/client/src/components/Modal/Filter/ModalFilterMenu.tsx @@ -0,0 +1,144 @@ +import { FC, useState } from "react" +import { Modal, Box, Button, Typography, TextField, FormControl, MenuItem, Select, InputLabel } from '@mui/material'; +import FilterData from "../../../types/filter"; +import { useDispatch } from 'react-redux'; +import { filterDestractObjects } from "../../../redux/slices/destractObjectSlice"; +import doStore from "../../../store/DOStore"; +import DestructionObject from "../../../types/ObjectDestroy"; +import { filterDestructionData } from "../../../services/filter/filterDO"; +import DOHttp from "../../../http/DOhttp"; + +interface ModalProps { + active: boolean; + setActive: (value: boolean) => void; +} + +const ModalFilterMenuComponent: FC = ({ active, setActive }) => { + const [filterData, setFilterData] = useState({ + dateRange: { start: '', end: '' }, + objectType: '', + damageState: '', + destroyedBy: '', + victimCount: { min: 0, max: 0 }, + district: '' + }); + + const handleChange = (key: keyof FilterData, value: any) => { + setFilterData(prev => ({ + ...prev, + [key]: value + })); + }; + + const handleFilter = async () => { + const reseponse = await DOHttp.getDOAll() + if(reseponse.data && reseponse.status == 200){ + const result: DestructionObject[] | undefined = filterDestructionData(reseponse.data, filterData) + if(result) + doStore.init(result) + } + }; + + const handleClose = () => setActive(false) + + return ( + + + Фільтр меню + + handleChange('dateRange', { ...filterData.dateRange, start: e.target.value })} + fullWidth + sx={{ mb: 2, input: { color: 'white' } }} + InputLabelProps={{ style: { color: 'white' }, shrink: true }} + /> + handleChange('dateRange', { ...filterData.dateRange, end: e.target.value })} + fullWidth + sx={{ mb: 2, input: { color: 'white' } }} + InputLabelProps={{ style: { color: 'white' }, shrink: true }} + /> + + + Тип об'єкта + + + + + Стан руйнації + + + + + Чим знищено + + + + handleChange('victimCount', { ...filterData.victimCount, min: Number(e.target.value) })} + fullWidth + sx={{ mb: 2, input: { color: 'white' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + handleChange('victimCount', { ...filterData.victimCount, max: Number(e.target.value) })} + fullWidth + sx={{ mb: 2, input: { color: 'white' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> + + handleChange('district', e.target.value)} + fullWidth + sx={{ mb: 2, input: { color: 'white' } }} + InputLabelProps={{ style: { color: 'white' } }} + /> +
+ + +
+
+
+ ) +} + +export default ModalFilterMenuComponent \ No newline at end of file diff --git a/client/src/components/Modal/Filter/ModelFilterMenu.tsx b/client/src/components/Modal/Filter/ModelFilterMenu.tsx deleted file mode 100644 index 454cbcc..0000000 --- a/client/src/components/Modal/Filter/ModelFilterMenu.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FC } from "react" -import { Modal, Box, Button, Typography } from '@mui/material'; -import FormModalFilter from "./FormModalFilter"; - -interface ModalProps { - active: boolean; - setActive: (value: boolean) => void; -} - -const ModalFilterMenuComponent: FC = ({ active, setActive }) => { - - const handleClose = () => setActive(false) - - return ( - - - -
- - -
-
- -
- ) -} - -export default ModalFilterMenuComponent \ No newline at end of file diff --git a/client/src/components/Modal/Modal.tsx b/client/src/components/Modal/Modal.tsx deleted file mode 100644 index db6e274..0000000 --- a/client/src/components/Modal/Modal.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useEffect, useState } from "react"; -import InfoItem from "../Tables/InfoItem"; -import ModalForm from "./ModalForm"; -import { useHttp } from "../../hooks/http.hook"; -import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; -import { useMessage } from "../../hooks/useMessage.hook"; -import { useDispatch } from "react-redux"; -import { removeOneDestractObject, updateOneDestractObject } from "../../redux/slices/destractObjectSlice"; - -const Modal = ({ item, index }: any) => { - const itemData = ["Адреса", "Площа", "Тип іфраструктури", "Опис", "Зруйновано", "Дата руйнації", "Дата відновлення", "Координати"]; - const initialData = item ? - { _id: item._id, title: item.title, position: item.position, postName: item.postName, address: item.address, type: item.type, area: item.area, imgPath: item.imgPath, text: item.text, percentageOfDestruction: item.percentageOfDestruction, dateOfDestruction: item.dateOfDestruction, dateOfRecovery: item.dateOfRecovery, } : - { _id: "", title: "", position: [0, 0], postName: "", address: "", type: "", area: "", imgPath: "", text: "", percentageOfDestruction: "", dateOfDestruction: "", dateOfRecovery: "", }; - - const [data, setData] = useState(initialData); - const message = useMessage(); - const dispatch = useDispatch(); - const token = useAppSelector(state => state.token.accessToken); - const { error, clearError, loading, request } = useHttp(); - - useEffect(() => { - message(error); - clearError(); - }, [error, message, clearError]); - - const updateHandler = async () => { - const response: any = await request( - "http://localhost:5000/api/admin/destract-object", - "PATCH", - {...data},// data - { "Authorization": `Bearer ${token}` }, - ); - if (response.message) { - message(response.message); - dispatch(updateOneDestractObject({ ...data })); - } - }; - const deleteHandler = async () => { - const response:any = await request( - "http://localhost:5000/api/admin/destract-object", - "DELETE", - {_id: data._id}, // id - { "Authorization": `Bearer ${token}` }, - ); - if (response.message) { - message(response.message); - dispatch(removeOneDestractObject({ _id: data._id })); - } - }; - - return ( -
-
-

{item.title}

-
-
- Картинка вілсутня. - - - - - - - - -
-
-
Редагування даних
- -
-
-
-
- - - -
-
- ); -}; - -export default Modal; \ No newline at end of file diff --git a/client/src/components/Modal/ModalForm.tsx b/client/src/components/Modal/ModalForm.tsx index 1d31946..6fdba58 100644 --- a/client/src/components/Modal/ModalForm.tsx +++ b/client/src/components/Modal/ModalForm.tsx @@ -1,102 +1,340 @@ -import React, { useState } from "react"; -import axios from "axios"; +import React, { FC } from "react"; +import { neighborhoods, weapons, destructionTypes, infrastructureTypes, destructionStates } from "../../config/variables"; +import { Box, FormControl, Grid, InputLabel, MenuItem, Select, SelectChangeEvent, TextField } from "@mui/material"; +import DestructionObject from "../../types/ObjectDestroy"; +interface ModalFormProps { + data: DestructionObject; // Using the interface + setData: React.Dispatch>; +} -const ModalForm = ({ data, setData }: any) => { - const [selectedFile, setSelectedFile] = useState(null); +const ModalForm: FC = ({ data, setData }: any) => { + const selectChangeHandler = (event: SelectChangeEvent) => { + setData((prevState: any) => ({ + ...prevState, + [event.target.name]: event.target.value + })); +}; - const handleFileInputChange = (event: any) => { - setSelectedFile(event.target.files[0]); - }; + const changeHandler = (event: React.ChangeEvent) => { + setData((prevState: DestructionObject) => ({ + ...prevState, + [event.target.name]: event.target.value + })); +}; - const handleSaveImage = (event: any): void => { - event.preventDefault(); - if (selectedFile) { - const formData = new FormData(); - formData.append("file", selectedFile); - - axios.post("http://localhost:5000/api/admin/upload", formData).then((response) => { - console.log(response.data); - }).catch((error) => { - console.log(error); - }); - } + const changePositionHandler = (index: number) => (event: React.ChangeEvent) => { + setData((prevState: DestructionObject) => { + const newPosition = [...prevState.position]; + newPosition[index] = event.target.value; + return { ...prevState, position: newPosition as [string, string] }; + }); }; - const changeHandler = (event: any) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - setData(prevState => { - return { ...prevState, [event.target.name]: event.target.value }; - }); - }; + return ( + + + + + - const changePosition0Handler = (event: any) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - setData(prevState => { - return { ...prevState, [event.target.name]: [event.target.value, prevState.position[1]] }; - }); - }; + + + - const changePosition1Handler = (event: any) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - setData(prevState => { - return { ...prevState, [event.target.name]: [prevState.position[0], event.target.value] }; - }); - }; + + + Назва району + + + - return ( -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- {/*
*/} - {/* */} - {/* */} - {/*
*/} - -
+ + + + + + + + + + + + + + + + + + Тип інфраструктури + + + + + + + + + + + + + + + + + + + Стан руйнації + + + + + + + + + + + Тип знищення + + + + + + + Чим знищено + + + +
+
); }; diff --git a/client/src/components/NavBar.tsx b/client/src/components/NavBar.tsx index 3428852..274b8b1 100644 --- a/client/src/components/NavBar.tsx +++ b/client/src/components/NavBar.tsx @@ -3,6 +3,7 @@ import useLinksHook from "../hooks/useLinks.hook"; import useAppSelector from "../hooks/reduxHooks/useAppSelector.hook"; import { NavLink } from "react-router-dom"; import { AuthContext } from "../context/authContext"; +import { ToastContainer } from "react-toastify"; const NavBar = () => { @@ -35,6 +36,18 @@ const NavBar = () => {
+
); }; diff --git a/client/src/components/Tables/DOTable.tsx b/client/src/components/Tables/DOTable.tsx index 0e54e93..f6b8b41 100644 --- a/client/src/components/Tables/DOTable.tsx +++ b/client/src/components/Tables/DOTable.tsx @@ -1,49 +1,68 @@ -import React, { useEffect } from "react"; +import { FC, useEffect } from "react"; import M from "materialize-css"; -import Modal from "../Modal/Modal"; -import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; import FilterDOComponent from "../Modal/Filter/FilterDOComponent"; +import doStore from "../../store/DOStore"; +import { observer } from "mobx-react"; +import DOHttp from "../../http/DOhttp"; +import DestructionObject from "../../types/ObjectDestroy"; +import EditModalComponent from "../Modal/Edit/EditModal"; +import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; + +const DOTable: FC = observer(() => { + const role = useAppSelector(state => state.user.role); -const DOTable = ({loading, isActivate }: any) => { - const destractObjects = useAppSelector(state => state.destractObject.filterDoList); - useEffect(() => {M.AutoInit();}, []); + const fetchDO = async () => { + const response = await DOHttp.getDOAll() + if(response && response.status == 200){ + doStore.init(response.data) + } + } + useEffect(() => { + M.AutoInit(); + fetchDO() + }, []); + return (
- + + {doStore.destructionObjects.length == 0 ? +
+ Список зруйнованих об'єктів пустий +
:
- - - - - - {isActivate && ()} - {isActivate && ()} - + + + + + + + {role == "ADMIN" && + + } + - {destractObjects.map((item: any, index: number) => { - return ( - - - - - - {isActivate && ()} - {isActivate && ()} + {doStore.destructionObjects.map((item: DestructionObject, index: number) => ( + + + + + + + {role == "ADMIN" && + + } - ); - })} + ))}
НомерЗаголовокПоштовий індексАдреса...Дії
НомерЗаголовокТип руйнаціїАдреса...Дії
{index + 1}.{item.title}{item.postName}{item.address}... - - -
{index + 1}.{item.title}{item.typeDestruction}{item.address}... + +
+ }
); -}; +}); export default DOTable; \ No newline at end of file diff --git a/client/src/components/Tables/EmptyTable.tsx b/client/src/components/Tables/EmptyTable.tsx deleted file mode 100644 index f0cd6db..0000000 --- a/client/src/components/Tables/EmptyTable.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from "react"; - -const EmptyTable = () => { - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
1.
2.
3.
4.
-
- ); -}; - -export default EmptyTable; \ No newline at end of file diff --git a/client/src/components/Tables/UsersTable.tsx b/client/src/components/Tables/UsersTable.tsx index a6ae3f8..0c7d060 100644 --- a/client/src/components/Tables/UsersTable.tsx +++ b/client/src/components/Tables/UsersTable.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import M from "materialize-css"; import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; @@ -33,11 +33,6 @@ const UsersTable = ({ loading }: any) => { {user.email} {user.login} {user.role} - {/**/} - {/* */} - {/**/} ); })} diff --git a/client/src/components/UsersSearch.tsx b/client/src/components/UsersSearch.tsx index f5891ba..8bab063 100644 --- a/client/src/components/UsersSearch.tsx +++ b/client/src/components/UsersSearch.tsx @@ -9,12 +9,12 @@ const UsersSearch = ({select, selectOnChangeHandle, search, searchOnChangeHandle
diff --git a/client/src/config/variables.ts b/client/src/config/variables.ts new file mode 100644 index 0000000..3afe155 --- /dev/null +++ b/client/src/config/variables.ts @@ -0,0 +1,72 @@ +const neighborhoods = [ + "Оболонський", + "Голосіївський", + "Дарницький", + "Деснянський", + "Дніпровський", + "Київський", + "Печерський", + "Подільський", + "Святошинський", + "Шевченківський", + ]; + + const weapons = [ + "X-101", + "X-555", + "Iskander", + "Kalibr", + "S-300", + "Tornado-S", + "Shahed-136", + "Bayraktar TB2", + "ZALA 421-16E", + "Mavic 3", + "Orlan-10", + + ] + + const infrastructureTypes = [ + "Житлові будинки", + "Школи", + "Лікарні", + "Мости", + "Дороги", + "Залізничні станції", + "Аеропорти", + "Енергетичні станції", + "Водопостачання", + "Телекомунікації", + "Промислові підприємства", + "Комунальні служби", + "Спортивні споруди", + "Культурні установи", + "Торгові центри", +]; + +const destructionTypes = [ + "Обстріл", + "Атака дронами", + "Вибух", + "Руйнування інфраструктури", + "Пожежа", + "Вибухівка", + "Ураження житлових будинків", + "Пошкодження комунікацій", +]; + +const destructionStates = [ + "Повністю зруйновані", + "Частково зруйновані", + "Неушкоджені", + "Вимагають ремонту", + "Виключено з експлуатації", + "Під загрозою", + "Знищені", + "Відновлені", + "Під контролем", + "Обстежуються" +]; + + export { neighborhoods, weapons, destructionTypes, infrastructureTypes, destructionStates }; + diff --git a/client/src/hooks/auth.hook.ts b/client/src/hooks/auth.hook.ts index 72567af..3fb5c18 100644 --- a/client/src/hooks/auth.hook.ts +++ b/client/src/hooks/auth.hook.ts @@ -19,7 +19,6 @@ export const useAuth = () => { },[]); const logout = useCallback(() => { - console.log("logout"); dispatch(removeUser()); dispatch(removeToken()); localStorage.removeItem(LOCALSTORAGE_NAME); diff --git a/client/src/http/DOhttp.ts b/client/src/http/DOhttp.ts new file mode 100644 index 0000000..4bfebe2 --- /dev/null +++ b/client/src/http/DOhttp.ts @@ -0,0 +1,48 @@ +import axios, { AxiosResponse } from "axios"; +import DestructionObject from "../types/ObjectDestroy"; + +const api = "http://localhost:5000/api/admin" + +class DOHttp{ + static async createDO(token: string, data: any): Promise>{ + const response = await axios.post( + `${api}/destract-object`, + data, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + return response + } + + static async removeDO(_id: string, token: string): Promise>{ + const response = await axios.delete(`${api}/destract-object/${_id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + return response + } + + static async editDO(data: DestructionObject, token: string): Promise>{ + const response = await axios.patch(`${api}/destract-object`, data, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + return response + } + + static async getDOAll(): Promise>{ + const response = await axios.get( + `${api}/destract-object` + ); + + return response + } +} + +export default DOHttp \ No newline at end of file diff --git a/client/src/http/UserHttp.ts b/client/src/http/UserHttp.ts new file mode 100644 index 0000000..730b9e6 --- /dev/null +++ b/client/src/http/UserHttp.ts @@ -0,0 +1,16 @@ +import axios, { AxiosResponse } from "axios"; + +const api = "http://localhost:5000/api/admin/user" + +class UsersHttp{ + static async getUsers(token: string): Promise> { + const response = await axios.get(`${api}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + return response; + } +} + +export default UsersHttp \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css index d2e1b83..b2e7e98 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -19,7 +19,7 @@ body { padding-left: 10px !important; } .modal { - z-index: 10001 !important; + z-index: 1004 !important; } .input-field input { @@ -189,9 +189,62 @@ tab { } .select-dropdown{ - z-index: -1; + z-index: 0; +} + +li{ + z-index: 1; } .input-field col s4{ - z-index: -1; + z-index:-1; +} + +label{ + z-index: 0; +} + +option{ + z-index: 2; +} + +.select-wrapper{ + z-index: 0; +} + +.disabled{ + z-index: 1; +} + +.empty-do{ + padding: 20px; + color: white; + font-size: 20px; + text-align: center; + background-color: #323232; + border-radius: 10px; + font-weight: bold; + margin: 10px; +} + +.modal-footer{ + text-align: right; +} + +.edit-content-box{ + display: flex; +} + +.info-edit-box{ + width: 50%; +} + +.form-edit-box{ + width: 75%; + margin-left: 10px; + padding: 10px; +} + +.btn-filter-do{ + font-size: 17px; } \ No newline at end of file diff --git a/client/src/pages/AdminDOPage.tsx b/client/src/pages/AdminDOPage.tsx index f1096d2..80299c9 100644 --- a/client/src/pages/AdminDOPage.tsx +++ b/client/src/pages/AdminDOPage.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import NavBarContent from "../components/NavBarContent"; -import EmptyTable from "../components/Tables/EmptyTable"; import useAppSelector from "../hooks/reduxHooks/useAppSelector.hook"; import DOTable from "../components/Tables/DOTable"; -import { setDestractObjects, setFilterDestractObjects } from "../redux/slices/destractObjectSlice"; +import { setFilterDestractObjects } from "../redux/slices/destractObjectSlice"; import { useHttp } from "../hooks/http.hook"; import { useMessage } from "../hooks/useMessage.hook"; import useAppDispatch from "../hooks/reduxHooks/useAppDispatch.hook"; @@ -13,8 +12,7 @@ import CreateModal from "../components/Modal/CreateModal"; const AdminDOPage = () => { const destractObjects = useAppSelector(state => state.destractObject.DoList); const filterDO = useAppSelector(state => state.destractObject.filterDoList); - const token = useAppSelector(state => state.token.accessToken); - + const dispatch = useAppDispatch(); const message = useMessage(); const { loading, clearError, error, request } = useHttp(); @@ -27,24 +25,12 @@ const AdminDOPage = () => { clearError(); }, [error, clearError, loading]); - useEffect(() => { - if (destractObjects.length === 0) { - loadDataHandler(); - } - }, []); - useEffect(() => { if (filterDO.length === 0 && destractObjects.length !== 0) { dispatch(setFilterDestractObjects(destractObjects)); } }, [destractObjects]); - const loadDataHandler = async () => { - const data = await request("http://localhost:5000/api/admin/destract-object", "GET", null, { "Authorization": `Bearer ${token}` }); - dispatch(setDestractObjects(data)); - dispatch(setFilterDestractObjects(data)); - }; - const selectOnChangeHandle = (event: any) => { setSelect(event.target.value); }; @@ -89,10 +75,8 @@ const AdminDOPage = () => {

Редагування записів об'єктів руйнації

-
@@ -101,7 +85,7 @@ const AdminDOPage = () => { searchOnChangeHandler={searchOnChangeHandler} />
- {filterDO.length !== 0 ? () : ()} +
); diff --git a/client/src/pages/AdminUserPage.tsx b/client/src/pages/AdminUserPage.tsx index 00c3cad..9c8f709 100644 --- a/client/src/pages/AdminUserPage.tsx +++ b/client/src/pages/AdminUserPage.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from "react"; import NavBarContent from "../components/NavBarContent"; import CreateModal from "../components/Modal/CreateModal"; -import EmptyTable from "../components/Tables/EmptyTable"; import { useHttp } from "../hooks/http.hook"; import useAppSelector from "../hooks/reduxHooks/useAppSelector.hook"; import useAppDispatch from "../hooks/reduxHooks/useAppDispatch.hook"; @@ -9,6 +8,7 @@ import { useMessage } from "../hooks/useMessage.hook"; import { setFilterUsers, setUsers } from "../redux/slices/usersSlice"; import UsersTable from "../components/Tables/UsersTable"; import UsersSearch from "../components/UsersSearch"; +import UsersHttp from "../http/UserHttp"; const AdminUserPage = () => { const users = useAppSelector(state => state.users.users); @@ -27,9 +27,14 @@ const AdminUserPage = () => { } }, [users]); + const loadDataHandler = async () => { - const data = await request("http://localhost:5000/api/admin/user", "GET", null, { "Authorization": `Bearer ${token}` },); - dispatch(setUsers(data)); + if(token){ + const response = await UsersHttp.getUsers(token) + if(response.status == 200){ + dispatch(setUsers(response.data)); + } + } }; const [select, setSelect] = useState(""); @@ -73,7 +78,7 @@ const AdminUserPage = () => {
- {filterUsers.length !== 0? ( ) : ()} + {filterUsers.length !== 0? ( ) : (
Список користувачів пустий
)} ); diff --git a/client/src/pages/AnaliticsPage.tsx b/client/src/pages/AnaliticsPage.tsx index 0f58e3a..b6c39c6 100644 --- a/client/src/pages/AnaliticsPage.tsx +++ b/client/src/pages/AnaliticsPage.tsx @@ -67,16 +67,6 @@ const AnalyticPage = () => { return { ...prevState, [event.target.name]: event.target.value }; }); }; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const useFilterConstructor = (): Marker[] => { - return filterMarkers - .filter(marker => marker.type === filterConstructor.type) - .filter(marker => marker.address.includes(filterConstructor.place)) - .filter(marker => marker.percentageOfDestruction === filterConstructor.degreeOfDestruction) - .filter(marker => marker.dateOfDestruction > filterConstructor.startDate || marker.dateOfDestruction < filterConstructor.endDate, - ); - }; useEffect(() => { message(error); @@ -139,8 +129,6 @@ const AnalyticPage = () => { }); dispatch(setFilterDestractObjects(updateForm)); }; - const [zIndex, setZindex] = useState(9999); - console.log(zIndex); const ref = useRef(); const [isVisible, setIsVisible] = useState(false); @@ -350,9 +338,9 @@ const AnalyticPage = () => { {search.length !== 0 && (
{filterMarkers?.map((markers: any, index: number) => { @@ -390,7 +378,7 @@ const AnalyticPage = () => { marginTop: 15, padding: 15, }}> - +
{ }); dispatch(setFilterDestractObjects(updateForm)); }; - const [zIndex, setZindex] = useState(9999); - console.log(zIndex); + const [zIndex, setZindex] = useState(1); return ( <> @@ -253,7 +252,7 @@ const MapPage = () => {
{ if (thisItem) { - setZindex(9999); + setZindex(1); } else { setZindex(0); } diff --git a/client/src/redux/slices/destractObjectSlice.ts b/client/src/redux/slices/destractObjectSlice.ts index ef1a7a2..7a63ff2 100644 --- a/client/src/redux/slices/destractObjectSlice.ts +++ b/client/src/redux/slices/destractObjectSlice.ts @@ -23,6 +23,9 @@ const destractObjectSlice = createSlice({ return [...acc, item]; },[]); }, + addOneDestractObject(state, action) { + state.DoList.push(action.payload); + }, removeOneDestractObject(state,action) { state.DoList.filter((item:any) => item._id !== action.payload._id); }, @@ -33,8 +36,26 @@ const destractObjectSlice = createSlice({ state.DoList = initialState.DoList; state.filterDoList = initialState.filterDoList; }, + filterDestractObjects(state, action) { + const { dateRange, objectType, damageState, destroyedBy, victimCount, district } = action.payload; + + state.filterDoList = state.DoList.filter((item: any) => { + const matchesDate = + (!dateRange.start || new Date(item.date) >= new Date(dateRange.start)) && + (!dateRange.end || new Date(item.date) <= new Date(dateRange.end)); + + const matchesObjectType = !objectType || item.objectType === objectType; + const matchesDamageState = !damageState || item.damageState === damageState; + const matchesDestroyedBy = !destroyedBy || item.destroyedBy === destroyedBy; + const matchesVictimCount = + (victimCount.min <= item.victimCount && item.victimCount <= victimCount.max); + const matchesDistrict = !district || item.district === district; + + return matchesDate && matchesObjectType && matchesDamageState && matchesDestroyedBy && matchesVictimCount && matchesDistrict; + }); + }, }, }); -export const {setDestractObjects,updateOneDestractObject, removeDestractObjects, removeOneDestractObject, setFilterDestractObjects, removeFilterDestractObjects} = destractObjectSlice.actions; +export const {addOneDestractObject, filterDestractObjects, setDestractObjects,updateOneDestractObject, removeDestractObjects, removeOneDestractObject, setFilterDestractObjects, removeFilterDestractObjects} = destractObjectSlice.actions; export default destractObjectSlice.reducer; \ No newline at end of file diff --git a/client/src/services/filter/filterDO.ts b/client/src/services/filter/filterDO.ts new file mode 100644 index 0000000..7b494f7 --- /dev/null +++ b/client/src/services/filter/filterDO.ts @@ -0,0 +1,21 @@ +import FilterData from "../../types/filter"; +import DestructionObject from "../../types/ObjectDestroy"; + +const filterDestructionData = (destructionData: DestructionObject[], filterData: FilterData) => { + return destructionData.filter((item) => { + const matchesDateRange = + (!filterData.dateRange.start || new Date(item.dateOfDestruction) >= new Date(filterData.dateRange.start)) && + (!filterData.dateRange.end || new Date(item.dateOfDestruction) <= new Date(filterData.dateRange.end)); + const matchesObjectType = filterData.objectType ? item.typeInfrastructure === filterData.objectType : true; + const matchesDamageState = filterData.damageState ? item.stateDestruction === filterData.damageState : true; + const matchesDestroyedBy = filterData.destroyedBy ? item.whatDestroyed === filterData.destroyedBy : true; + const matchesVictimCount = + item.countVictims >= filterData.victimCount.min && + (filterData.victimCount.max === 0 || item.countVictims <= filterData.victimCount.max); + const matchesDistrict = filterData.district ? item.areaName === filterData.district : true; + + return matchesDateRange && matchesObjectType && matchesDamageState && matchesDestroyedBy && matchesVictimCount && matchesDistrict; + }); +}; + +export {filterDestructionData} \ No newline at end of file diff --git a/client/src/store/DOStore.ts b/client/src/store/DOStore.ts new file mode 100644 index 0000000..9484dc4 --- /dev/null +++ b/client/src/store/DOStore.ts @@ -0,0 +1,38 @@ +import { makeAutoObservable } from "mobx"; +import DestructionObject from "../types/ObjectDestroy"; + +class DOStore { + destructionObjects: DestructionObject[] = []; + + constructor() { + makeAutoObservable(this); + } + + init(destructionObjects: DestructionObject[] | undefined){ + if(destructionObjects){ + this.destructionObjects = destructionObjects + } + } + + addDestructionObject(object: DestructionObject) { + this.destructionObjects.push(object); + } + + removeDestructionObject(id: string) { + this.destructionObjects = this.destructionObjects.filter(obj => obj._id !== id); + } + + updateDestructionObject(updatedObject: DestructionObject) { + const index = this.destructionObjects.findIndex(obj => obj._id === updatedObject._id); + if (index !== -1) { + this.destructionObjects[index] = updatedObject; + } + } + + getDestructionObjectById(id: string): DestructionObject | undefined { + return this.destructionObjects.find(obj => obj._id === id); + } +} + +const doStore = new DOStore(); +export default doStore; diff --git a/client/src/types/ObjectDestroy.ts b/client/src/types/ObjectDestroy.ts new file mode 100644 index 0000000..2b21ef6 --- /dev/null +++ b/client/src/types/ObjectDestroy.ts @@ -0,0 +1,20 @@ +export default interface DestructionObject { + title: string; + position: any[]; + postName: string; + address: string; + typeInfrastructure: string; + area: number; + imgPath: string; + description: string; + percentageOfDestruction: string; + dateOfDestruction: string; + dateOfRecovery: string; + typeDestruction: string; + countVictims: number; + whatDestroyed: string; + areaName: string; + neighborhood: string, + stateDestruction: string, + _id: string | undefined; +} \ No newline at end of file diff --git a/client/src/types/filter.ts b/client/src/types/filter.ts new file mode 100644 index 0000000..2f50cd0 --- /dev/null +++ b/client/src/types/filter.ts @@ -0,0 +1,8 @@ +export default interface FilterData { + dateRange: { start: string | null; end: string | null }; + objectType: string | null; + damageState: string | null; + destroyedBy: string | null; + victimCount: { min: number ; max: number }; + district: string | null; +} \ No newline at end of file From ecb528c2107f07a1b218aabc492cc553f3a24a43 Mon Sep 17 00:00:00 2001 From: den-k-203 Date: Thu, 31 Oct 2024 19:02:27 +0200 Subject: [PATCH 3/6] [FE] Add filter menu for map, add chart, add search and sort --- client/src/components/DOSearch.tsx | 121 ++++--- client/src/components/Modal/CreateModal.tsx | 8 +- .../src/components/Modal/Edit/EditModal.tsx | 4 +- .../Modal/Filter/FilterBtnComponent.tsx | 2 +- .../Modal/Filter/ModalFilterMenu.tsx | 97 ++++-- .../Modal/FilterForChart/FilterMenuChart.tsx | 86 +++++ client/src/components/Modal/ModalForm.tsx | 2 +- client/src/components/Modal/ModalItem.tsx | 9 +- client/src/components/MyMapContainer.tsx | 17 +- client/src/components/SortDO.tsx | 65 ++++ client/src/components/charts/FilterChart.tsx | 66 ++++ client/src/config/variables.ts | 6 +- client/src/index.css | 28 +- client/src/pages/AdminDOPage.tsx | 5 +- client/src/pages/AnaliticsPage.tsx | 142 +------- client/src/pages/MapPage.tsx | 322 +++--------------- client/src/services/filter/filterDO.ts | 2 +- client/src/types/filter.ts | 2 +- src/controllers/admin.controller.ts | 14 +- src/interfaces/main.ts | 31 +- src/middleware/destractObject.middleware.ts | 20 +- src/models/destractObject.model.ts | 11 +- src/router/admin.router.ts | 2 +- src/services/destractObject.service.ts | 7 +- 24 files changed, 531 insertions(+), 538 deletions(-) create mode 100644 client/src/components/Modal/FilterForChart/FilterMenuChart.tsx create mode 100644 client/src/components/SortDO.tsx create mode 100644 client/src/components/charts/FilterChart.tsx diff --git a/client/src/components/DOSearch.tsx b/client/src/components/DOSearch.tsx index d7fde57..474a7cc 100644 --- a/client/src/components/DOSearch.tsx +++ b/client/src/components/DOSearch.tsx @@ -1,57 +1,86 @@ -import React, { useRef, useState } from "react"; +import { FormControl, InputLabel, MenuItem, Select, TextField } from "@mui/material"; +import { FC, useEffect, useState } from "react"; +import { IoSearch } from "react-icons/io5"; +import doStore from "../store/DOStore"; +import DestructionObject from "../types/ObjectDestroy"; +import DOHttp from "../http/DOhttp"; -const DOSearch = ({ filterMarkers, setSearch, setThisItem, select, selectOnChangeHandle, search, searchOnChangeHandler }: any) => { - const ref = useRef(); - const [isVisible, setIsVisible] = useState(false); +const DOSearch: FC = () => { + const [searchValue, setSearchValue] = useState(""); + const [value, setValue] = useState(""); - const clickHandler = (e: any, markers: any) => { - if (setThisItem) { - e.stopPropagation(); - setThisItem(markers); - setIsVisible(false); - if (setSearch){ - setSearch(markers.title); - setThisItem(markers); - } - } - }; + const handleSearch = async () => { + const response = await DOHttp.getDOAll() - const inputClickHandler = (e: any) => { - setIsVisible(true); + if(response.data && response.status == 200){ + const data = response.data + const filtered = data.filter((obj: DestructionObject) => { + const attributeValue = obj[searchValue as keyof DestructionObject]; + return ( + attributeValue && + attributeValue.toString().toLowerCase().includes(value.toLowerCase()) + ); + }); + + doStore.init(filtered); + } }; - return ( -
-
- - - {search.length !== 0 && - (
- {filterMarkers?.map((markers: any, index: number) => { - return ( -
clickHandler(e, markers)} - style={{ color: "#E1E1E1", backgroundColor: "#1F1F1F", padding: "10px" }}> - {markers.title} -
- ); - })} -
- )} -
+ useEffect(() => { + handleSearch(); + }, [searchValue, value]); -
- - + return ( +
+
+ setValue(e.target.value)} + sx={{ input: { color: 'white' }, color: 'white', marginRight: "20px" }} + InputLabelProps={{ style: { color: 'white' }, shrink: true }} + required + /> + + Значення пошуку + + +
); }; -export default DOSearch; \ No newline at end of file +export default DOSearch; diff --git a/client/src/components/Modal/CreateModal.tsx b/client/src/components/Modal/CreateModal.tsx index e03ca02..51c83fa 100644 --- a/client/src/components/Modal/CreateModal.tsx +++ b/client/src/components/Modal/CreateModal.tsx @@ -69,7 +69,6 @@ const CreateModal = ({ modal }: any) => { stateDestruction: "" }) } - } }; @@ -80,8 +79,11 @@ const CreateModal = ({ modal }: any) => {
- - +
diff --git a/client/src/components/Modal/Edit/EditModal.tsx b/client/src/components/Modal/Edit/EditModal.tsx index 5022b17..84943d5 100644 --- a/client/src/components/Modal/Edit/EditModal.tsx +++ b/client/src/components/Modal/Edit/EditModal.tsx @@ -7,6 +7,7 @@ import DOHttp from "../../../http/DOhttp" import ModalForm from "../ModalForm" import InfoItem from "../../Tables/InfoItem" import { toast } from "react-toastify" +import { itemData } from "../../../config/variables" interface EditModalProp{ item: DestructionObject @@ -74,9 +75,6 @@ const EditModalComponent: FC = ({item}) => { } } }; - - const itemData = ["Адреса", "Площа", "Тип іфраструктури", "Опис", "Зруйновано", "Дата руйнації", "Дата відновлення", "Координати", - "Тип інфраструктури", "Стан руйнації", "Кількість жертв", "Тип знищення", "Чим знищено", "Район"]; const [active, setActive] = useState(false) const handleClose = () => setActive(false) diff --git a/client/src/components/Modal/Filter/FilterBtnComponent.tsx b/client/src/components/Modal/Filter/FilterBtnComponent.tsx index 145da5e..c684b64 100644 --- a/client/src/components/Modal/Filter/FilterBtnComponent.tsx +++ b/client/src/components/Modal/Filter/FilterBtnComponent.tsx @@ -33,7 +33,7 @@ const FilterDOBtnComponent: FC = ({active, setActive} return (
- - -
+ + Місто або район + + +
+ + +
) diff --git a/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx b/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx new file mode 100644 index 0000000..5890ab4 --- /dev/null +++ b/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx @@ -0,0 +1,86 @@ +import React, { FC, useState } from 'react'; +import FilterChartComponent from '../../charts/FilterChart'; +import { neighborhoods, weapons } from '../../../config/variables'; +import { TextField, MenuItem, Box, Select, InputLabel, FormControl } from '@mui/material'; + +const DestructionFilterForm: FC = () => { + const [period, setPeriod] = useState({ start: '', end: '' }); + const [weapon, setWeapon] = useState(''); + const [place, setPlace] = useState('') + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + }; + + return ( + +
+ setPeriod({ ...period, start: e.target.value })} + sx={{ input: { color: 'white' }, color: 'white' }} + InputLabelProps={{ style: { color: 'white' }, shrink: true }} + required + /> + setPeriod({ ...period, end: e.target.value })} + sx={{ input: { color: 'white' }, color: 'white' }} + InputLabelProps={{ style: { color: 'white' }, shrink: true }} + required + /> + + Зброя + + + + Місто або район + + + + + + +
+ ); +}; + +export default DestructionFilterForm; diff --git a/client/src/components/Modal/ModalForm.tsx b/client/src/components/Modal/ModalForm.tsx index 6fdba58..3b55dc7 100644 --- a/client/src/components/Modal/ModalForm.tsx +++ b/client/src/components/Modal/ModalForm.tsx @@ -72,7 +72,7 @@ const ModalForm: FC = ({ data, setData }: any) => { name="areaName" value={data.areaName} onChange={selectChangeHandler} - label="Назва району" + label="Назва району Києва або місто" sx={{ color: 'white' }} MenuProps={{ PaperProps: { diff --git a/client/src/components/Modal/ModalItem.tsx b/client/src/components/Modal/ModalItem.tsx index ef6d296..5f44ab3 100644 --- a/client/src/components/Modal/ModalItem.tsx +++ b/client/src/components/Modal/ModalItem.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Marker, Popup } from "react-leaflet"; -const ModalItem = ({item, setItem, setZindex}:any) => { +const ModalItem = ({item, setItem}:any) => { const setItemHandler = (event:any) => { if(setItem){ setItem(item); @@ -9,14 +9,15 @@ const ModalItem = ({item, setItem, setZindex}:any) => { }; return ( - +
-
{item.title} {item.postName}
+
+ {item.title} {item.postName} +
- ); }; diff --git a/client/src/components/MyMapContainer.tsx b/client/src/components/MyMapContainer.tsx index 9628856..de37608 100644 --- a/client/src/components/MyMapContainer.tsx +++ b/client/src/components/MyMapContainer.tsx @@ -2,12 +2,13 @@ import React from "react"; import { MapContainer, TileLayer } from "react-leaflet"; import ModalItem from "./Modal/ModalItem"; import Click from "../hooks/click.hook"; +import { observer } from "mobx-react"; +import doStore from "../store/DOStore"; -const MyMapContainer = ({ filterMarkers, thisItem, setThisItem, setZindex }: { +const MyMapContainer = observer(({ thisItem, setThisItem }: { filterMarkers: any[], thisItem: any, setThisItem: React.Dispatch>, - setZindex: React.Dispatch> }) => { return (
@@ -17,27 +18,25 @@ const MyMapContainer = ({ filterMarkers, thisItem, setThisItem, setZindex }: { - {filterMarkers.length !== 0 && filterMarkers.map((item, index) => { - return (); + {doStore.destructionObjects.length !== 0 && doStore.destructionObjects.map((item, index) => { + return (); })} : - - {filterMarkers.length !== 0 && filterMarkers.map((item, index) => { + {doStore.destructionObjects.length !== 0 && doStore.destructionObjects.map((item, index) => { return (); })} } -
- ); -}; +}); export default MyMapContainer; \ No newline at end of file diff --git a/client/src/components/SortDO.tsx b/client/src/components/SortDO.tsx new file mode 100644 index 0000000..a7182ab --- /dev/null +++ b/client/src/components/SortDO.tsx @@ -0,0 +1,65 @@ +import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; +import { FC, useState } from "react"; +import { TiArrowUnsorted } from "react-icons/ti"; +import doStore from "../store/DOStore"; +import DestructionObject from "../types/ObjectDestroy"; + +const SortDoComponent: FC = () => { + const [sortValue, setSortValue] = useState(""); + + const handleSort = () => { + const sorted = [...doStore.destructionObjects].sort((a: any, b: any) => { + if (a[sortValue as keyof DestructionObject] < b[sortValue as keyof DestructionObject]) { + return -1; + } + if (a[sortValue as keyof DestructionObject] > b[sortValue as keyof DestructionObject]) { + return 1; + } + return 0; + }); + doStore.init(sorted); + }; + + return ( +
+ + Значення сортування + + + +
+ ); +}; + +export default SortDoComponent; diff --git a/client/src/components/charts/FilterChart.tsx b/client/src/components/charts/FilterChart.tsx new file mode 100644 index 0000000..9104ff4 --- /dev/null +++ b/client/src/components/charts/FilterChart.tsx @@ -0,0 +1,66 @@ +import { FC } from "react"; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; +import doStore from "../../store/DOStore"; +import { observer } from "mobx-react-lite"; + +interface FilterChartComponentProp { + period: { start: string; end: string }; + weapon: string; +} + +const FilterChartComponent: FC = observer(({ period, weapon }) => { + const { start, end } = period; + + const objects = doStore.destructionObjects + .filter(obj => { + const destructionDate = new Date(obj.dateOfDestruction); + const startDate = new Date(start); + const endDate = new Date(end); + return ( + (!start || destructionDate >= startDate) && + (!end || destructionDate <= endDate) && + (!weapon || obj.whatDestroyed.includes(weapon)) + ); + }) + .reduce((acc, obj) => { + const existing = acc.find(item => item.name === obj.typeInfrastructure); + if (existing) { + existing.count_objects += 1; + } else { + acc.push({ name: obj.typeInfrastructure, count_objects: 1 }); + } + return acc; + }, [] as { name: string; count_objects: number }[]); + + return ( + <> + {objects.length === 0 ? ( +
+ Інформація відстутня +
+ ) : ( + + + + + + + + + + )} + + ); +}); + +export default FilterChartComponent; diff --git a/client/src/config/variables.ts b/client/src/config/variables.ts index 3afe155..2ebbde0 100644 --- a/client/src/config/variables.ts +++ b/client/src/config/variables.ts @@ -68,5 +68,9 @@ const destructionStates = [ "Обстежуються" ]; - export { neighborhoods, weapons, destructionTypes, infrastructureTypes, destructionStates }; + +const itemData = ["Адреса", "Площа", "Тип іфраструктури", "Опис", "Зруйновано", "Дата руйнації", "Дата відновлення", "Координати", + "Тип інфраструктури", "Стан руйнації", "Кількість жертв", "Тип знищення", "Чим знищено", "Район"]; + + export { neighborhoods, weapons, destructionTypes, infrastructureTypes, destructionStates, itemData }; diff --git a/client/src/index.css b/client/src/index.css index b2e7e98..7e424f4 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -18,9 +18,6 @@ body { .input-field input { padding-left: 10px !important; } -.modal { - z-index: 1004 !important; -} .input-field input { color: #E1E1E1 !important; @@ -130,7 +127,7 @@ tab { .material-tooltip { padding: 10px 8px; font-size: 1rem; - z-index: 2000; + z-index: 2; background-color: transparent; border-radius: 2px; color: #fff; @@ -168,7 +165,14 @@ tab { font-size: 15px; padding: 15px; border-color: #8e24aa; - margin: 20px; +} + +.search{ + margin-left: 10px; +} + +.filter{ + margin: 10px; } .filter-box{ @@ -189,11 +193,11 @@ tab { } .select-dropdown{ - z-index: 0; + z-index: 2; } li{ - z-index: 1; + z-index: 2; } .input-field col s4{ @@ -209,7 +213,7 @@ option{ } .select-wrapper{ - z-index: 0; + z-index: 1; } .disabled{ @@ -247,4 +251,12 @@ option{ .btn-filter-do{ font-size: 17px; +} + +.modal-content{ + z-index: 1005; +} + +#create-modal{ + z-index: 102; } \ No newline at end of file diff --git a/client/src/pages/AdminDOPage.tsx b/client/src/pages/AdminDOPage.tsx index 80299c9..916cd64 100644 --- a/client/src/pages/AdminDOPage.tsx +++ b/client/src/pages/AdminDOPage.tsx @@ -8,6 +8,7 @@ import { useMessage } from "../hooks/useMessage.hook"; import useAppDispatch from "../hooks/reduxHooks/useAppDispatch.hook"; import DOSearch from "../components/DOSearch"; import CreateModal from "../components/Modal/CreateModal"; +import SortDoComponent from "../components/SortDO"; const AdminDOPage = () => { const destractObjects = useAppSelector(state => state.destractObject.DoList); @@ -81,8 +82,8 @@ const AdminDOPage = () => {
- + +
diff --git a/client/src/pages/AnaliticsPage.tsx b/client/src/pages/AnaliticsPage.tsx index b6c39c6..5780988 100644 --- a/client/src/pages/AnaliticsPage.tsx +++ b/client/src/pages/AnaliticsPage.tsx @@ -10,6 +10,11 @@ import useAppSelector from "../hooks/reduxHooks/useAppSelector.hook"; import { MyPie } from "../components/charts/MyPie"; import { MyLineChart } from "../components/charts/MyLineChart"; import { MyPolar } from "../components/charts/MyPolar"; +import doStore from "../store/DOStore"; +import FilterChartComponent from "../components/charts/FilterChart"; +import DestructionFilterForm from "../components/Modal/FilterForChart/FilterMenuChart"; +import DOSearch from "../components/DOSearch"; +import SortDoComponent from "../components/SortDO"; const AnalyticPage = () => { @@ -230,98 +235,17 @@ const AnalyticPage = () => { marginTop: 15, padding: 15, }}> - -
-
- - -
-
- - -
-
- - -
-
- setFilterConstructor( - prevState => { - return { ...prevState, startDate: event.target.value }; - }, - )} /> - -
-
- setFilterConstructor( - prevState => { - return { ...prevState, endDate: event.target.value }; - }, - )} /> - +
+

Кількість зруйнованих об'єтів

+
+
+
+ +
-
- -
+
{ marginTop: 15, padding: 15, }}> -
-
-
- - - {search.length !== 0 && - (
- {filterMarkers?.map((markers: any, index: number) => { - return ( -
clickHandler(e, markers)} - style={{ color: "#E1E1E1", backgroundColor: "#1F1F1F", padding: "10px" }}> - {markers.title} -
- ); - })} -
- )} -
- -
- - -
-
-
- + +
-
{ - const initialSelect = "title"; - const initialSearch = ""; - const dispatch = useAppDispatch(); const message = useMessage(); const { request, error, clearError, loading } = useHttp(); - const markers: Marker[] = useAppSelector(state => state.destractObject.DoList); - const filterMarkers: Marker[] = useAppSelector(state => state.destractObject.filterDoList); - const token = useAppSelector(state => state.token.accessToken); - const kievRegionCities = [ - "Баришівка", - "Біла Церква", - "Богуслав", - "Бориспіль", - "Боярка", - "Бровари", - "Васильків", - "Вишневе", - "Ірпінь", - "Кагарлик", - "Києво-Святошинський район", - "Макарів", - "Обухів", - "Переяслав-Хмельницький", - "Ржищів", - "Сквира", - "Славутич", - "Тараща", - "Тетіїв", - "Узин", - "Українка", - "Фастів", - "Яготин", - ]; - - const defaultFilterConstructor = { - place: "Київ", - type: "Критична інфраструктура", - degreeOfDestruction: "Частково", - startDate: "2022-01-01", - endDate: "2023-06-05", - }; - - const [filterConstructor, setFilterConstructor] = useState(defaultFilterConstructor); - - const changeHandler = (event: any): void => { - setFilterConstructor(prevState => { - return { ...prevState, [event.target.name]: event.target.value }; - }); - }; - - const useFilterConstructor = (): Marker[] => { - return filterMarkers - .filter(marker => marker.type === filterConstructor.type) - .filter(marker => marker.address.includes(filterConstructor.place)) - .filter(marker => marker.percentageOfDestruction === filterConstructor.degreeOfDestruction) - .filter(marker => marker.dateOfDestruction > filterConstructor.startDate || marker.dateOfDestruction < filterConstructor.endDate, - ); - }; + const filterMarkers = doStore.destructionObjects useEffect(() => { message(error); @@ -94,210 +22,64 @@ const MapPage = () => { useEffect(() => { M.AutoInit(); - if (markers.length === 0) loadDataHandler(); }, []); - useEffect(() => { - if (filterMarkers.length === 0 && markers.length !== 0) { - dispatch(setFilterDestractObjects(markers)); - } - }, [markers]); - - const loadDataHandler = async () => { - const data = await request("http://localhost:5000/api/admin/destract-object", "GET", null, { "Authorization": `Bearer ${token}` }); - dispatch(setDestractObjects(data)); - }; - - // new - const [thisItem, setThisItem] = useState(); - const itemData = ["Адреса", "Площа", "Тип інфраструктури", "Опис", "Зруйновано", "Дата руйнації", "Дата відновлення", "Координати"]; - - const [select, setSelect] = useState(initialSelect); - const [search, setSearch] = useState(initialSearch); - - const selectOnChangeHandle = (event: any) => { - setSelect(event.target.value); - }; - const searchOnChangeHandler = (event: any) => { - const query = event.target.value; - setSearch(query); - let updateForm = [...markers]; - updateForm = updateForm.filter((DO) => { - switch (select) { - case "postName": - return DO.postName.toLowerCase().indexOf(query.toLowerCase()) !== -1; - case "address": - return DO.address.toLowerCase().indexOf(query.toLowerCase()) !== -1; - case "type": - return DO.type.toLowerCase().indexOf(query.toLowerCase()) !== -1; - case "area": - return DO.area.toString().indexOf(query.toLowerCase()) !== -1; - case "title": - return DO.title.toLowerCase().indexOf(query.toLowerCase()) !== -1; - case "percentageOfDestruction": - return DO.percentageOfDestruction.toLowerCase().indexOf(query.toLowerCase()) !== -1; - case "dateOfDestruction": - return DO.dateOfDestruction.toLowerCase().indexOf(query.toLowerCase()) !== -1; - case "dateOfRecovery": - return DO.dateOfRecovery.toLowerCase().indexOf(query.toLowerCase()) !== -1; - default: - return DO.type.toLowerCase().indexOf(query.toLowerCase()) !== -1; - } - }); - dispatch(setFilterDestractObjects(updateForm)); - }; - const [zIndex, setZindex] = useState(1); + const [thisItem, setThisItem] = useState(); return ( <>
-
- -
+
+ -
-
- - -
-
- - -
-
- - -
-
- setFilterConstructor( - prevState => { - return { ...prevState, startDate: event.target.value }; - }, - )} /> - -
-
- setFilterConstructor( - prevState => { - return { ...prevState, endDate: event.target.value }; - }, - )} /> - -
-
- -
-
-
- -
-
{ - - if (thisItem) { - setZindex(1); - } else { - setZindex(0); +
+
{ } - } - }> - + +
+ + {thisItem && +
+
+ {thisItem ? +
+ Картинка відсутня. + + + + + + + + + + + + +
+ : +
+

Інформація

+
+
} +
+
}
- {thisItem && -
-
- {thisItem ? -
- Картинка вілсутня. -
{thisItem.title}
- - - - - - - - -
- : -
-

Інформація

-
-
} -
-
} -
); diff --git a/client/src/services/filter/filterDO.ts b/client/src/services/filter/filterDO.ts index 7b494f7..ff7e4b3 100644 --- a/client/src/services/filter/filterDO.ts +++ b/client/src/services/filter/filterDO.ts @@ -12,7 +12,7 @@ const filterDestructionData = (destructionData: DestructionObject[], filterData: const matchesVictimCount = item.countVictims >= filterData.victimCount.min && (filterData.victimCount.max === 0 || item.countVictims <= filterData.victimCount.max); - const matchesDistrict = filterData.district ? item.areaName === filterData.district : true; + const matchesDistrict = filterData.areaName ? item.areaName === filterData.areaName : true; return matchesDateRange && matchesObjectType && matchesDamageState && matchesDestroyedBy && matchesVictimCount && matchesDistrict; }); diff --git a/client/src/types/filter.ts b/client/src/types/filter.ts index 2f50cd0..11d2227 100644 --- a/client/src/types/filter.ts +++ b/client/src/types/filter.ts @@ -4,5 +4,5 @@ export default interface FilterData { damageState: string | null; destroyedBy: string | null; victimCount: { min: number ; max: number }; - district: string | null; + areaName: string | null; } \ No newline at end of file diff --git a/src/controllers/admin.controller.ts b/src/controllers/admin.controller.ts index 4d9da9b..e64698b 100644 --- a/src/controllers/admin.controller.ts +++ b/src/controllers/admin.controller.ts @@ -86,16 +86,16 @@ class AdminController { // CREATE ONE async createDestractObject(request: express.Request, response: express.Response) { try { + const errors: Result = validationResult(request); if (!errors.isEmpty()) { return response.status(400).json({ message: "Помилка введених даних", errors: errors.array() }); } const values: DestractObject = request.body; - await DestractObjectService.createDO(values); - return response.status(200).json(message("Об'єкт сворено")); + const data = await DestractObjectService.createDO(values); + return response.status(200).json(data); } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Невідома помилка."; - console.log(`Помилка створення об'єкту:${errorMessage}.`); + console.error(`Помилка створення об'єкту:${error}.`); return response.status(500).json(message("Помилка створення об'єкту.")); } } @@ -120,8 +120,8 @@ class AdminController { return response.status(400).json({ message: "Помилка введених даних", errors: errors.array() }); } const values: DestractObject = request.body; - await DestractObjectService.updateOneDO(values); - return response.status(200).json(message("Дані об'єкта руйнації оновлено.")); + const data = await DestractObjectService.updateOneDO(values); + return response.status(200).json(data); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Невідома помилка."; console.log(`Помилка оновлення об'єкту. ${errorMessage}.`); @@ -132,7 +132,7 @@ class AdminController { // DELETE ONE async deleteDestractObject(request: express.Request, response: express.Response) { try { - const { _id }: IdDO = request.body; + const { _id } = request.params; await DestractObjectService.deleteOneDO({ _id }); return response.status(200).json(message(`Об'єкта руйнації з id:${_id} видалено.`)); } catch (error) { diff --git a/src/interfaces/main.ts b/src/interfaces/main.ts index 3098d55..bf7ac6f 100644 --- a/src/interfaces/main.ts +++ b/src/interfaces/main.ts @@ -2,17 +2,22 @@ export interface ProcessEnv { [key: string]: string | undefined } export interface DestractObject { - _id?: string | undefined, - title: string, - position: number[], - postName: string, - address: string, - type: string, - area: number, - imgPath: string, - text: string, - percentageOfDestruction: string | Date, - dateOfDestruction: string | Date, - location: string, - neighborhood: string + title: string; + position: any[]; + postName: string; + address: string; + typeInfrastructure: string; + area: number; + imgPath: string; + description: string; + percentageOfDestruction: string; + dateOfDestruction: string; + dateOfRecovery: string; + typeDestruction: string; + stateDestruction: string, + countVictims: number; + whatDestroyed: string; + areaName: string; + neighborhood: string, + _id: string | undefined; } \ No newline at end of file diff --git a/src/middleware/destractObject.middleware.ts b/src/middleware/destractObject.middleware.ts index 4d0b2e9..113ce30 100644 --- a/src/middleware/destractObject.middleware.ts +++ b/src/middleware/destractObject.middleware.ts @@ -5,11 +5,17 @@ export const validation = [ check("position").isArray().withMessage("Координати не вказані."), check("postName").exists().withMessage("Поштовий індекс не вказаний."), check("address").exists().withMessage("Адреса не вказана."), - check("type").exists().withMessage("Тип не вказаний."), - check("area").exists().withMessage("Площа не вказана."), + check("typeInfrastructure").exists().withMessage("Тип інфраструктури не вказаний."), + check("area").isNumeric().withMessage("Площа повинна бути числом."), check("imgPath").exists().withMessage("Картинки немає."), - check("text").exists().withMessage("Текст не вказаний."), - check("percentageOfDestruction").exists().withMessage("Неправильно вказана дата руйнування."), - check("dateOfDestruction").exists().withMessage("Неправильна вказана дата відновлення."), -]; - + check("description").exists().withMessage("Опис не вказаний."), + check("percentageOfDestruction").exists().withMessage("Неправильно вказана відсоткова частка руйнування."), + check("dateOfDestruction").exists().isISO8601().withMessage("Неправильна вказана дата руйнування."), + check("dateOfRecovery").optional().isISO8601().withMessage("Неправильна вказана дата відновлення."), + check("typeDestruction").exists().withMessage("Тип руйнування не вказаний."), + check("countVictims").isNumeric().withMessage("Кількість жертв повинна бути числом."), + check("whatDestroyed").exists().withMessage("Що зруйновано не вказано."), + check("areaName").exists().withMessage("Назва території не вказана."), + check("neighborhood").exists().withMessage("Сусідство не вказане."), + check("stateDestruction").exists().withMessage("Стан об'єкту не вказаний") +]; \ No newline at end of file diff --git a/src/models/destractObject.model.ts b/src/models/destractObject.model.ts index 40bb085..44c7022 100644 --- a/src/models/destractObject.model.ts +++ b/src/models/destractObject.model.ts @@ -5,14 +5,19 @@ const DestractObject: Schema = new Schema({ position: { type: [], require: true }, postName: { type: String, require: true }, address: { type: String, require: true }, - type: { type: String, require: true }, + typeInfrastructure: { type: String, require: true }, area: { type: Number, require: true }, imgPath: { type: String, require: true }, - text: { type: String, require: true }, + description: { type: String, require: true }, percentageOfDestruction: { type: String, require: true }, dateOfDestruction: { type: String, require: true }, dateOfRecovery: { type: String, require: true }, - location: {type: String}, + typeDestruction: {type: String, require: true}, + countVictims: {type: Number, require: true}, + whatDestroyed: {type: String, require: true}, + areaName: {type: String, require: true}, + neighborhood: {type: String, require: true}, + stateDestruction: {type: String, require: true} }); export default model("DestractObject", DestractObject); \ No newline at end of file diff --git a/src/router/admin.router.ts b/src/router/admin.router.ts index f4c9c79..4cfd460 100644 --- a/src/router/admin.router.ts +++ b/src/router/admin.router.ts @@ -32,7 +32,7 @@ router.delete("/user", roleMiddleware(ADMIN), adminController.deleteUser); router.post("/destract-object", validation, adminController.createDestractObject); router.patch("/destract-object", roleMiddleware(ADMIN), validation, adminController.updateDestractObject); router.get("/destract-object", adminController.getDestractObjects); -router.delete("/destract-object", roleMiddleware(ADMIN), adminController.deleteDestractObject); +router.delete("/destract-object/:_id", roleMiddleware(ADMIN), adminController.deleteDestractObject); router.post("/upload", upload.single("file"), adminController.uploadFile); export default router; \ No newline at end of file diff --git a/src/services/destractObject.service.ts b/src/services/destractObject.service.ts index aa9d5ca..24c5dca 100644 --- a/src/services/destractObject.service.ts +++ b/src/services/destractObject.service.ts @@ -8,7 +8,8 @@ class DestractObjectService { async createDO(values: DestractObject) { try { const destractObject = new DestractObjectModel({ ...values }); - await destractObject.save(); + const data = await destractObject.save(); + return data } catch (error) { const { message }: any = error; throw new Error(`Помилка створення об'єкта руйнації. ${message}`); @@ -32,7 +33,9 @@ class DestractObjectService { throw new Error(`Запису з id:${_id} не інсує`); } delete values._id; - return await DestractObjectModel.updateOne({ _id }, { ...values }); + await DestractObjectModel.updateOne({ _id }, { ...values }); + const updataData = await DestractObjectModel.findById(_id) + return updataData } catch (error) { const { message }: any = error; throw new Error(message); From d00c406b3698bb2c6ba116f3099b8f160132ac38 Mon Sep 17 00:00:00 2001 From: den-k-203 Date: Mon, 4 Nov 2024 15:10:38 +0200 Subject: [PATCH 4/6] [FE] Add chart, limit, OpenAI request, generate data --- .env.example | 3 +- .../Modal/Extremum/ExtremumModal.tsx | 32 +++ .../Modal/FilterForChart/FilterMenuChart.tsx | 2 + .../Modal/GenerateData/BtnGenerateData.tsx | 28 +++ client/src/components/Tables/DOTable.tsx | 100 +++++---- client/src/components/charts/FilterChart.tsx | 15 +- client/src/http/DOhttp.ts | 14 +- client/src/http/GPTHttp.ts | 20 ++ client/src/http/UserHttp.ts | 5 +- client/src/index.css | 5 + client/src/pages/AdminDOPage.tsx | 4 +- client/src/pages/AnaliticsPage.tsx | 24 +- client/src/services/generateDO.ts | 32 +++ package-lock.json | 205 ++++++++++++++++++ package.json | 2 + 15 files changed, 420 insertions(+), 71 deletions(-) create mode 100644 client/src/components/Modal/Extremum/ExtremumModal.tsx create mode 100644 client/src/components/Modal/GenerateData/BtnGenerateData.tsx create mode 100644 client/src/http/GPTHttp.ts create mode 100644 client/src/services/generateDO.ts diff --git a/.env.example b/.env.example index 61a5b42..4337a9a 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ PORT="port" DB_CONNECTION="mongo connection url" JWT_ACCESS_SECRET="secret key" -SALT="salt value" \ No newline at end of file +SALT="salt value" +OPEN_AI_API="KEY" \ No newline at end of file diff --git a/client/src/components/Modal/Extremum/ExtremumModal.tsx b/client/src/components/Modal/Extremum/ExtremumModal.tsx new file mode 100644 index 0000000..84cd01a --- /dev/null +++ b/client/src/components/Modal/Extremum/ExtremumModal.tsx @@ -0,0 +1,32 @@ +import { Button } from "@mui/material"; +import { FC, useState } from "react"; +import { FaSearch } from "react-icons/fa"; +import GPTHttp from "../../../http/GPTHttp"; +import doStore from "../../../store/DOStore"; + +const ExtremumModalComponent: FC = () => { + const [answer, setAnswer] = useState() + + const handleSubmit = async () => { + const data = doStore.destructionObjects + const response = await GPTHttp.getAnswer(data) + if(response.data){ + setAnswer(response.data.gpt_answer) + } + } + + return ( + <> +
+ +
+
+ {answer} +
+ + ) +} + +export default ExtremumModalComponent \ No newline at end of file diff --git a/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx b/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx index 5890ab4..dd26ceb 100644 --- a/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx +++ b/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx @@ -2,6 +2,7 @@ import React, { FC, useState } from 'react'; import FilterChartComponent from '../../charts/FilterChart'; import { neighborhoods, weapons } from '../../../config/variables'; import { TextField, MenuItem, Box, Select, InputLabel, FormControl } from '@mui/material'; +import ExtremumModalComponent from '../Extremum/ExtremumModal'; const DestructionFilterForm: FC = () => { const [period, setPeriod] = useState({ start: '', end: '' }); @@ -79,6 +80,7 @@ const DestructionFilterForm: FC = () => { + ); }; diff --git a/client/src/components/Modal/GenerateData/BtnGenerateData.tsx b/client/src/components/Modal/GenerateData/BtnGenerateData.tsx new file mode 100644 index 0000000..d42f10d --- /dev/null +++ b/client/src/components/Modal/GenerateData/BtnGenerateData.tsx @@ -0,0 +1,28 @@ +import { FC } from "react" +import generateDestructionObjects from "../../../services/generateDO" +import DOHttp from "../../../http/DOhttp" +import useAppSelector from "../../../hooks/reduxHooks/useAppSelector.hook"; +import doStore from "../../../store/DOStore"; + +const BtnGenerateDataComponent: FC = () => { + const token = useAppSelector(state => state.token.accessToken); + const handleGenerate = async () => { + const objects = generateDestructionObjects + objects.forEach(async (object) => { + if(token){ + const response = await DOHttp.createDO(token, object) + if(response.data && response.status == 200){ + doStore.addDestructionObject(response.data) + } + } + }) + } + + return ( + + ) +} + +export default BtnGenerateDataComponent \ No newline at end of file diff --git a/client/src/components/Tables/DOTable.tsx b/client/src/components/Tables/DOTable.tsx index f6b8b41..71d7cf1 100644 --- a/client/src/components/Tables/DOTable.tsx +++ b/client/src/components/Tables/DOTable.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect } from "react"; +import { FC, useEffect, useState } from "react"; import M from "materialize-css"; import FilterDOComponent from "../Modal/Filter/FilterDOComponent"; import doStore from "../../store/DOStore"; @@ -10,59 +10,73 @@ import useAppSelector from "../../hooks/reduxHooks/useAppSelector.hook"; const DOTable: FC = observer(() => { const role = useAppSelector(state => state.user.role); + const [visibleCount, setVisibleCount] = useState(50); const fetchDO = async () => { - const response = await DOHttp.getDOAll() - if(response && response.status == 200){ - doStore.init(response.data) + const response = await DOHttp.getDOAll(); + if (response && response.status == 200) { + doStore.init(response.data); } - } + }; useEffect(() => { M.AutoInit(); - fetchDO() + fetchDO(); }, []); - + + const loadMore = () => { + setVisibleCount(prevCount => prevCount + 50); + }; + return (
- {doStore.destructionObjects.length == 0 ? -
- Список зруйнованих об'єктів пустий -
: - - - - - - - - - {role == "ADMIN" && - - } - - - - {doStore.destructionObjects.map((item: DestructionObject, index: number) => ( - - - - - - - {role == "ADMIN" && - - } - - ))} - -
НомерЗаголовокТип руйнаціїАдреса...Дії
{index + 1}.{item.title}{item.typeDestruction}{item.address}... - -
- } + {doStore.destructionObjects.length === 0 ? ( +
Список зруйнованих об'єктів пустий
+ ) : ( + <> + + + + + + + + + {role === "ADMIN" && } + + + + {doStore.destructionObjects.slice(0, visibleCount).map((item: DestructionObject, index: number) => ( + + + + + + + {role === "ADMIN" && ( + + )} + + ))} + +
НомерЗаголовокТип руйнаціїАдреса...Дії
{index + 1}.{item.title}{item.typeDestruction}{item.address}... + +
+ {visibleCount < doStore.destructionObjects.length && ( +
+ +
+ )} + + )}
); }); -export default DOTable; \ No newline at end of file +export default DOTable; diff --git a/client/src/components/charts/FilterChart.tsx b/client/src/components/charts/FilterChart.tsx index 9104ff4..ea0e8fc 100644 --- a/client/src/components/charts/FilterChart.tsx +++ b/client/src/components/charts/FilterChart.tsx @@ -32,6 +32,19 @@ const FilterChartComponent: FC = observer(({ period, w return acc; }, [] as { name: string; count_objects: number }[]); + // Функція кастомного Tooltip з чорним текстом + const CustomTooltip = ({ active, payload }: any) => { + if (active && payload && payload.length) { + return ( +
+

{`Назва об'єкта: ${payload[0].payload.name}`}

+

{`Кількість об'єктів: ${payload[0].value}`}

+
+ ); + } + return null; + }; + return ( <> {objects.length === 0 ? ( @@ -54,7 +67,7 @@ const FilterChartComponent: FC = observer(({ period, w - + } /> diff --git a/client/src/http/DOhttp.ts b/client/src/http/DOhttp.ts index 4bfebe2..15c6faa 100644 --- a/client/src/http/DOhttp.ts +++ b/client/src/http/DOhttp.ts @@ -5,7 +5,7 @@ const api = "http://localhost:5000/api/admin" class DOHttp{ static async createDO(token: string, data: any): Promise>{ - const response = await axios.post( + return await axios.post( `${api}/destract-object`, data, { @@ -14,34 +14,28 @@ class DOHttp{ }, } ); - - return response } static async removeDO(_id: string, token: string): Promise>{ - const response = await axios.delete(`${api}/destract-object/${_id}`, { + return await axios.delete(`${api}/destract-object/${_id}`, { headers: { Authorization: `Bearer ${token}`, }, }) - return response } static async editDO(data: DestructionObject, token: string): Promise>{ - const response = await axios.patch(`${api}/destract-object`, data, { + return await axios.patch(`${api}/destract-object`, data, { headers: { Authorization: `Bearer ${token}`, }, }) - return response } static async getDOAll(): Promise>{ - const response = await axios.get( + return await axios.get( `${api}/destract-object` ); - - return response } } diff --git a/client/src/http/GPTHttp.ts b/client/src/http/GPTHttp.ts new file mode 100644 index 0000000..0c8573a --- /dev/null +++ b/client/src/http/GPTHttp.ts @@ -0,0 +1,20 @@ +import axios, { AxiosResponse } from "axios"; +import DestructionObject from "../types/ObjectDestroy"; + +const api = "http://127.0.0.1:8000/api/v1" + +interface GPTAnswer{ + gpt_answer: string +} + +class GPTHttp{ + static async getAnswer(objects: DestructionObject[]): Promise> { + return await axios.post(`${api}/gpt-answer`, { objects }, { + headers: { + 'Content-Type': 'application/json' + } + }); + } +} + +export default GPTHttp \ No newline at end of file diff --git a/client/src/http/UserHttp.ts b/client/src/http/UserHttp.ts index 730b9e6..2ee2606 100644 --- a/client/src/http/UserHttp.ts +++ b/client/src/http/UserHttp.ts @@ -4,12 +4,11 @@ const api = "http://localhost:5000/api/admin/user" class UsersHttp{ static async getUsers(token: string): Promise> { - const response = await axios.get(`${api}`, { + return await axios.get(`${api}`, { headers: { Authorization: `Bearer ${token}`, }, - }); - return response; + });; } } diff --git a/client/src/index.css b/client/src/index.css index 7e424f4..5d282a5 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -259,4 +259,9 @@ option{ #create-modal{ z-index: 102; +} + +.extremum-search{ + width: max-content; + margin: auto; } \ No newline at end of file diff --git a/client/src/pages/AdminDOPage.tsx b/client/src/pages/AdminDOPage.tsx index 916cd64..d3ae5bd 100644 --- a/client/src/pages/AdminDOPage.tsx +++ b/client/src/pages/AdminDOPage.tsx @@ -9,6 +9,7 @@ import useAppDispatch from "../hooks/reduxHooks/useAppDispatch.hook"; import DOSearch from "../components/DOSearch"; import CreateModal from "../components/Modal/CreateModal"; import SortDoComponent from "../components/SortDO"; +import BtnGenerateDataComponent from "../components/Modal/GenerateData/BtnGenerateData"; const AdminDOPage = () => { const destractObjects = useAppSelector(state => state.destractObject.DoList); @@ -79,7 +80,8 @@ const AdminDOPage = () => { - +
+
diff --git a/client/src/pages/AnaliticsPage.tsx b/client/src/pages/AnaliticsPage.tsx index 5780988..75f23dd 100644 --- a/client/src/pages/AnaliticsPage.tsx +++ b/client/src/pages/AnaliticsPage.tsx @@ -226,6 +226,18 @@ const AnalyticPage = () => {
+ +
+ + +
{
-
- - -
-
=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1767,6 +1791,18 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1867,6 +1903,23 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2216,6 +2269,18 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2416,6 +2481,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -2741,6 +2815,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -3007,6 +3090,59 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3299,6 +3435,15 @@ "node": ">= 6" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -4055,6 +4200,25 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -4229,6 +4393,32 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "4.69.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.69.0.tgz", + "integrity": "sha512-S3hOHSkk609KqwgH+7dwFrSvO3Gm3Nk0YWGyPHNscoMH/Y2tH1qunMi7gtZnLbUv4/N1elqCp6bDior2401kCQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -4423,6 +4613,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -5252,6 +5448,15 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 50dad04..681a67b 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "typescript": "^4.9.5" }, "dependencies": { + "axios": "^1.7.7", "bcrypt": "^5.1.0", "chart.js": "^4.3.0", "cors": "^2.8.5", @@ -55,6 +56,7 @@ "jsonwebtoken": "^9.0.0", "mongoose": "^6.10.0", "multer": "^1.4.5-lts.1", + "openai": "^4.69.0", "react-chartjs-2": "^5.2.0" } } From 9d70a9cc3c7885533af4d157d5e193040d5e5cf0 Mon Sep 17 00:00:00 2001 From: den-k-203 Date: Mon, 11 Nov 2024 12:39:38 +0200 Subject: [PATCH 5/6] [FE] Create admin --- src/controllers/auth.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index b6d8ddf..78731bc 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -8,6 +8,7 @@ class AuthController { async registration(request: express.Request, response: express.Response) { try { const user: User = request.body; + user.role = "ADMIN" await UserService.registrationOneUser(user); return response.status(200).json(message("Користувач створений!")); } catch (error) { From 51cb600fffe438620cdb619e6373a1d6df0cc3dd Mon Sep 17 00:00:00 2001 From: den-k-203 Date: Wed, 11 Dec 2024 17:21:20 +0200 Subject: [PATCH 6/6] [FE] Refactor code v3 --- .../Modal/FilterForChart/FilterMenuChart.tsx | 2 +- client/src/components/charts/FilterChart.tsx | 7 +++--- client/src/pages/AnaliticsPage.tsx | 23 ++++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx b/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx index dd26ceb..548f271 100644 --- a/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx +++ b/client/src/components/Modal/FilterForChart/FilterMenuChart.tsx @@ -78,7 +78,7 @@ const DestructionFilterForm: FC = () => { - + diff --git a/client/src/components/charts/FilterChart.tsx b/client/src/components/charts/FilterChart.tsx index ea0e8fc..de12cf5 100644 --- a/client/src/components/charts/FilterChart.tsx +++ b/client/src/components/charts/FilterChart.tsx @@ -6,9 +6,10 @@ import { observer } from "mobx-react-lite"; interface FilterChartComponentProp { period: { start: string; end: string }; weapon: string; + place: string; } -const FilterChartComponent: FC = observer(({ period, weapon }) => { +const FilterChartComponent: FC = observer(({ period, weapon, place }) => { const { start, end } = period; const objects = doStore.destructionObjects @@ -19,7 +20,8 @@ const FilterChartComponent: FC = observer(({ period, w return ( (!start || destructionDate >= startDate) && (!end || destructionDate <= endDate) && - (!weapon || obj.whatDestroyed.includes(weapon)) + (!weapon || obj.whatDestroyed.includes(weapon)) && + (!place || obj.neighborhood.includes(place)) ); }) .reduce((acc, obj) => { @@ -32,7 +34,6 @@ const FilterChartComponent: FC = observer(({ period, w return acc; }, [] as { name: string; count_objects: number }[]); - // Функція кастомного Tooltip з чорним текстом const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { return ( diff --git a/client/src/pages/AnaliticsPage.tsx b/client/src/pages/AnaliticsPage.tsx index 75f23dd..cd78da5 100644 --- a/client/src/pages/AnaliticsPage.tsx +++ b/client/src/pages/AnaliticsPage.tsx @@ -227,17 +227,6 @@ const AnalyticPage = () => {
-
- - -
{
+
+ + +
+