diff --git a/typescript/Frontend/lang/en.json b/typescript/Frontend/lang/en.json deleted file mode 100644 index c2937f320..000000000 --- a/typescript/Frontend/lang/en.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "alarms": { - "defaultMessage": "Alarms" - }, - "allInstallations": { - "defaultMessage": "All installations" - }, - "applyChanges": { - "defaultMessage": "Apply changes" - }, - "country": { - "defaultMessage": "Country" - }, - "customerName": { - "defaultMessage": "Customer name" - }, - "english": { - "defaultMessage": "English" - }, - "german": { - "defaultMessage": "German" - }, - "installation": { - "defaultMessage": "Installation" - }, - "location": { - "defaultMessage": "Location" - }, - "log": { - "defaultMessage": "Log" - }, - "logout": { - "defaultMessage": "Logout" - }, - "orderNumbers": { - "defaultMessage": "Order number" - }, - "region": { - "defaultMessage": "Region" - }, - "search": { - "defaultMessage": "Search" - }, - "updatedSuccessfully": { - "defaultMessage": "Updated successfully" - }, - "users": { - "defaultMessage": "Users" - } -} diff --git a/typescript/Frontend/package-lock.json b/typescript/Frontend/package-lock.json index b3b2c1d8a..1bcf739ce 100644 --- a/typescript/Frontend/package-lock.json +++ b/typescript/Frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", + "@minoru/react-dnd-treeview": "^3.4.1", "@mui/icons-material": "^5.11.0", "@mui/lab": "^5.0.0-alpha.120", "@mui/material": "^5.11.7", @@ -23,14 +24,21 @@ "@types/react-router-dom": "^5.3.3", "axios": "^1.3.1", "chart.js": "^4.2.1", + "css-loader": "^6.7.3", "formik": "^2.2.9", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dnd-scrolling": "^1.3.3", "react-dom": "^18.2.0", "react-intl": "^6.2.10", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "reactflow": "^11.5.6", + "sass": "^1.58.3", + "sass-loader": "^13.2.0", + "style-loader": "^3.3.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, @@ -3181,6 +3189,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", @@ -3191,6 +3204,82 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@minoru/react-dnd-treeview": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@minoru/react-dnd-treeview/-/react-dnd-treeview-3.4.1.tgz", + "integrity": "sha512-FnxWRY1VM++glt43hqtwSuGmE6K1vWvNWsf69KoADvXHnUAu85+EIdYD1ONgAdLdOzXL5rotl7E0FLeGidaDyg==", + "dependencies": { + "@juggle/resize-observer": "^3.3.1", + "dnd-multi-backend": "^7.0.0-alpha.4", + "framer-motion": "^6.2.8", + "react-dnd-html5-backend": "^16.0.1", + "react-dnd-touch-backend": "^16.0.1", + "react-use-measure": "^2.1.1" + }, + "peerDependencies": { + "react": "17.x || 18.x", + "react-dnd": "15.x || 16.x", + "react-dom": "17.x || 18.x" + } + }, + "node_modules/@motionone/animation": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.15.1.tgz", + "integrity": "sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==", + "dependencies": { + "@motionone/easing": "^10.15.1", + "@motionone/types": "^10.15.1", + "@motionone/utils": "^10.15.1", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/dom": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz", + "integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==", + "dependencies": { + "@motionone/animation": "^10.12.0", + "@motionone/generators": "^10.12.0", + "@motionone/types": "^10.12.0", + "@motionone/utils": "^10.12.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/easing": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.15.1.tgz", + "integrity": "sha512-6hIHBSV+ZVehf9dcKZLT7p5PEKHGhDwky2k8RKkmOvUoYP3S+dXsKupyZpqx5apjd9f+php4vXk4LuS+ADsrWw==", + "dependencies": { + "@motionone/utils": "^10.15.1", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/generators": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.15.1.tgz", + "integrity": "sha512-67HLsvHJbw6cIbLA/o+gsm7h+6D4Sn7AUrB/GPxvujse1cGZ38F5H7DzoH7PhX+sjvtDnt2IhFYF2Zp1QTMKWQ==", + "dependencies": { + "@motionone/types": "^10.15.1", + "@motionone/utils": "^10.15.1", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/types": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.15.1.tgz", + "integrity": "sha512-iIUd/EgUsRZGrvW0jqdst8st7zKTzS9EsKkP+6c6n4MPZoQHwiHuVtTQLD6Kp0bsBLhNzKIBlHXponn/SDT4hA==" + }, + "node_modules/@motionone/utils": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.15.1.tgz", + "integrity": "sha512-p0YncgU+iklvYr/Dq4NobTRdAPv9PveRDUXabPEeOjBLSO/1FNB2phNTZxOxpi1/GZwYpAoECEa0Wam+nsmhSw==", + "dependencies": { + "@motionone/types": "^10.15.1", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, "node_modules/@mui/base": { "version": "5.0.0-alpha.117", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.117.tgz", @@ -3631,6 +3720,21 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-dnd/asap": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", + "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" + }, + "node_modules/@react-dnd/invariant": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", + "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", + "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" + }, "node_modules/@reactflow/background": { "version": "11.1.8", "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.1.8.tgz", @@ -6448,6 +6552,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -7250,6 +7362,11 @@ "node": ">=10" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7327,6 +7444,17 @@ "node": ">= 10" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -7470,6 +7598,21 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/dnd-core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", + "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", + "dependencies": { + "@react-dnd/asap": "^5.0.1", + "@react-dnd/invariant": "^4.0.1", + "redux": "^4.2.0" + } + }, + "node_modules/dnd-multi-backend": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/dnd-multi-backend/-/dnd-multi-backend-7.1.3.tgz", + "integrity": "sha512-INOAt4p/5fkaAUpXu0I+ialm1Ewi9HmIhs/558RFrhBZ0s/XGL991w3A2GvBuaPQNsZJEzCJh0mv/0dswxfSfQ==" + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -9203,6 +9346,49 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/framer-motion": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz", + "integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==", + "dependencies": { + "@motionone/dom": "10.12.0", + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "popmotion": "11.0.3", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": ">=16.8 || ^17.0.0 || ^18.0.0", + "react-dom": ">=16.8 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, + "node_modules/framesync": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", + "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -9583,6 +9769,11 @@ "he": "bin/he" } }, + "node_modules/hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -9877,6 +10068,11 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/immutable": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", + "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -12737,6 +12933,11 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -13677,6 +13878,17 @@ "node": ">=4" } }, + "node_modules/popmotion": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", + "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", + "dependencies": { + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + } + }, "node_modules/postcss": { "version": "8.4.21", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", @@ -15266,6 +15478,69 @@ "node": ">=8" } }, + "node_modules/react-dnd": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", + "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "dependencies": { + "@react-dnd/invariant": "^4.0.1", + "@react-dnd/shallowequal": "^4.0.1", + "dnd-core": "^16.0.1", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "@types/hoist-non-react-statics": ">= 3.3.1", + "@types/node": ">= 12", + "@types/react": ">= 16", + "react": ">= 16.14" + }, + "peerDependenciesMeta": { + "@types/hoist-non-react-statics": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", + "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", + "dependencies": { + "dnd-core": "^16.0.1" + } + }, + "node_modules/react-dnd-scrolling": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.3.3.tgz", + "integrity": "sha512-IEUbvK7fPWzOpPXaMvYQJR+ZGDPUtOwNpAJeEqUdTqQb0quwaytr/LfiAFGbWI87D55InsGaTUDyWkfXURyLNA==", + "dependencies": { + "defaults": "^1.0.4", + "hoist-non-react-statics": "3.x", + "lodash.throttle": "^4.1.1", + "prop-types": "15.x", + "raf": "^3.4.1" + }, + "peerDependencies": { + "react": "16.x || 17.x || 18.x", + "react-dnd": "10.x || 11.x || 14.x || 15.x || 16.x", + "react-dom": "16.x || 17.x || 18.x" + } + }, + "node_modules/react-dnd-touch-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-16.0.1.tgz", + "integrity": "sha512-NonoCABzzjyWGZuDxSG77dbgMZ2Wad7eQiCd/ECtsR2/NBLTjGksPUx9UPezZ1nQ/L7iD130Tz3RUshL/ClKLA==", + "dependencies": { + "@react-dnd/invariant": "^4.0.1", + "dnd-core": "^16.0.1" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -15440,6 +15715,43 @@ "node": ">=10" } }, + "node_modules/react-scripts/node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, "node_modules/react-scripts/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -15474,6 +15786,18 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "dependencies": { + "debounce": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + } + }, "node_modules/reactflow": { "version": "11.5.6", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.5.6.tgz", @@ -15545,6 +15869,14 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15953,16 +16285,32 @@ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" }, + "node_modules/sass": { + "version": "1.58.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.3.tgz", + "integrity": "sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", "dependencies": { "klona": "^2.0.4", "neo-async": "^2.6.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", @@ -15970,7 +16318,7 @@ }, "peerDependencies": { "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" @@ -16579,6 +16927,15 @@ "webpack": "^5.0.0" } }, + "node_modules/style-value-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", + "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", + "dependencies": { + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -20458,6 +20815,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", @@ -20468,6 +20830,77 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "@minoru/react-dnd-treeview": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@minoru/react-dnd-treeview/-/react-dnd-treeview-3.4.1.tgz", + "integrity": "sha512-FnxWRY1VM++glt43hqtwSuGmE6K1vWvNWsf69KoADvXHnUAu85+EIdYD1ONgAdLdOzXL5rotl7E0FLeGidaDyg==", + "requires": { + "@juggle/resize-observer": "^3.3.1", + "dnd-multi-backend": "^7.0.0-alpha.4", + "framer-motion": "^6.2.8", + "react-dnd-html5-backend": "^16.0.1", + "react-dnd-touch-backend": "^16.0.1", + "react-use-measure": "^2.1.1" + } + }, + "@motionone/animation": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.15.1.tgz", + "integrity": "sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==", + "requires": { + "@motionone/easing": "^10.15.1", + "@motionone/types": "^10.15.1", + "@motionone/utils": "^10.15.1", + "tslib": "^2.3.1" + } + }, + "@motionone/dom": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz", + "integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==", + "requires": { + "@motionone/animation": "^10.12.0", + "@motionone/generators": "^10.12.0", + "@motionone/types": "^10.12.0", + "@motionone/utils": "^10.12.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, + "@motionone/easing": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.15.1.tgz", + "integrity": "sha512-6hIHBSV+ZVehf9dcKZLT7p5PEKHGhDwky2k8RKkmOvUoYP3S+dXsKupyZpqx5apjd9f+php4vXk4LuS+ADsrWw==", + "requires": { + "@motionone/utils": "^10.15.1", + "tslib": "^2.3.1" + } + }, + "@motionone/generators": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.15.1.tgz", + "integrity": "sha512-67HLsvHJbw6cIbLA/o+gsm7h+6D4Sn7AUrB/GPxvujse1cGZ38F5H7DzoH7PhX+sjvtDnt2IhFYF2Zp1QTMKWQ==", + "requires": { + "@motionone/types": "^10.15.1", + "@motionone/utils": "^10.15.1", + "tslib": "^2.3.1" + } + }, + "@motionone/types": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.15.1.tgz", + "integrity": "sha512-iIUd/EgUsRZGrvW0jqdst8st7zKTzS9EsKkP+6c6n4MPZoQHwiHuVtTQLD6Kp0bsBLhNzKIBlHXponn/SDT4hA==" + }, + "@motionone/utils": { + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.15.1.tgz", + "integrity": "sha512-p0YncgU+iklvYr/Dq4NobTRdAPv9PveRDUXabPEeOjBLSO/1FNB2phNTZxOxpi1/GZwYpAoECEa0Wam+nsmhSw==", + "requires": { + "@motionone/types": "^10.15.1", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, "@mui/base": { "version": "5.0.0-alpha.117", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.117.tgz", @@ -20676,6 +21109,21 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, + "@react-dnd/asap": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", + "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" + }, + "@react-dnd/invariant": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", + "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" + }, + "@react-dnd/shallowequal": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", + "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" + }, "@reactflow/background": { "version": "11.1.8", "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.1.8.tgz", @@ -22851,6 +23299,11 @@ "wrap-ansi": "^7.0.0" } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + }, "clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -23423,6 +23876,11 @@ "whatwg-url": "^8.0.0" } }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -23483,6 +23941,14 @@ "execa": "^5.0.0" } }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "requires": { + "clone": "^1.0.2" + } + }, "define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -23584,6 +24050,21 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "dnd-core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", + "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", + "requires": { + "@react-dnd/asap": "^5.0.1", + "@react-dnd/invariant": "^4.0.1", + "redux": "^4.2.0" + } + }, + "dnd-multi-backend": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/dnd-multi-backend/-/dnd-multi-backend-7.1.3.tgz", + "integrity": "sha512-INOAt4p/5fkaAUpXu0I+ialm1Ewi9HmIhs/558RFrhBZ0s/XGL991w3A2GvBuaPQNsZJEzCJh0mv/0dswxfSfQ==" + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -24862,6 +25343,45 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==" }, + "framer-motion": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz", + "integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==", + "requires": { + "@emotion/is-prop-valid": "^0.8.2", + "@motionone/dom": "10.12.0", + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "popmotion": "11.0.3", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + } + } + }, + "framesync": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", + "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "requires": { + "tslib": "^2.1.0" + } + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -25123,6 +25643,11 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -25348,6 +25873,11 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==" }, + "immutable": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", + "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -27411,6 +27941,11 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -28084,6 +28619,17 @@ } } }, + "popmotion": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", + "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", + "requires": { + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + } + }, "postcss": { "version": "8.4.21", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", @@ -29055,6 +29601,47 @@ } } }, + "react-dnd": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", + "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "requires": { + "@react-dnd/invariant": "^4.0.1", + "@react-dnd/shallowequal": "^4.0.1", + "dnd-core": "^16.0.1", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + } + }, + "react-dnd-html5-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", + "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", + "requires": { + "dnd-core": "^16.0.1" + } + }, + "react-dnd-scrolling": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.3.3.tgz", + "integrity": "sha512-IEUbvK7fPWzOpPXaMvYQJR+ZGDPUtOwNpAJeEqUdTqQb0quwaytr/LfiAFGbWI87D55InsGaTUDyWkfXURyLNA==", + "requires": { + "defaults": "^1.0.4", + "hoist-non-react-statics": "3.x", + "lodash.throttle": "^4.1.1", + "prop-types": "15.x", + "raf": "^3.4.1" + } + }, + "react-dnd-touch-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-16.0.1.tgz", + "integrity": "sha512-NonoCABzzjyWGZuDxSG77dbgMZ2Wad7eQiCd/ECtsR2/NBLTjGksPUx9UPezZ1nQ/L7iD130Tz3RUshL/ClKLA==", + "requires": { + "@react-dnd/invariant": "^4.0.1", + "dnd-core": "^16.0.1" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -29181,6 +29768,15 @@ "yallist": "^4.0.0" } }, + "sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -29207,6 +29803,14 @@ "prop-types": "^15.6.2" } }, + "react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "requires": { + "debounce": "^1.2.1" + } + }, "reactflow": { "version": "11.5.6", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.5.6.tgz", @@ -29262,6 +29866,14 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -29539,10 +30151,20 @@ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" }, + "sass": { + "version": "1.58.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.3.tgz", + "integrity": "sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==", + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, "sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", "requires": { "klona": "^2.0.4", "neo-async": "^2.6.2" @@ -30016,6 +30638,15 @@ "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", "requires": {} }, + "style-value-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", + "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", + "requires": { + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" + } + }, "stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", diff --git a/typescript/Frontend/package.json b/typescript/Frontend/package.json index 0d166a2ae..803841d8f 100644 --- a/typescript/Frontend/package.json +++ b/typescript/Frontend/package.json @@ -5,6 +5,7 @@ "dependencies": { "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", + "@minoru/react-dnd-treeview": "^3.4.1", "@mui/icons-material": "^5.11.0", "@mui/lab": "^5.0.0-alpha.120", "@mui/material": "^5.11.7", @@ -18,14 +19,21 @@ "@types/react-router-dom": "^5.3.3", "axios": "^1.3.1", "chart.js": "^4.2.1", + "css-loader": "^6.7.3", "formik": "^2.2.9", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dnd-scrolling": "^1.3.3", "react-dom": "^18.2.0", "react-intl": "^6.2.10", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "reactflow": "^11.5.6", + "sass": "^1.58.3", + "sass-loader": "^13.2.0", + "style-loader": "^3.3.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, diff --git a/typescript/Frontend/src/App.tsx b/typescript/Frontend/src/App.tsx index f33d0aafd..14e903ff0 100644 --- a/typescript/Frontend/src/App.tsx +++ b/typescript/Frontend/src/App.tsx @@ -7,11 +7,10 @@ import { IntlProvider } from "react-intl"; import { useState } from "react"; import en from "./lang/en.json"; import de from "./lang/de.json"; -import LanguageSelect from "./components/LanguageSelect"; -import LogoutButton from "./components/LogoutButton"; -import Installations from "./routes/Installations"; - -import Groups from "./routes/Groups"; +import Installations from "./components/Installations/Installations"; +import LanguageSelect from "./components/Layout/LanguageSelect"; +import LogoutButton from "./components/Layout/LogoutButton"; +import Groups from "./components/Groups/Groups"; const App = () => { const { token, setToken, removeToken } = useToken(); diff --git a/typescript/Frontend/src/Login.tsx b/typescript/Frontend/src/Login.tsx index 10939760d..6ec794962 100644 --- a/typescript/Frontend/src/Login.tsx +++ b/typescript/Frontend/src/Login.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; import { Alert, Button, CircularProgress, Grid } from "@mui/material"; import Container from "@mui/material/Container"; -import InnovenergyTextfield from "./components/InnovenergyTextfield"; -import { axiosConfigWithoutToken } from "./config/axiosConfig"; +import axiosConfig, { axiosConfigWithoutToken } from "./config/axiosConfig"; +import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield"; const loginUser = async (username: string, password: string) => { return axiosConfigWithoutToken.post("/Login", { @@ -11,18 +11,30 @@ const loginUser = async (username: string, password: string) => { }); }; -const Login = ({ setToken }: { setToken: any }) => { +const Login = ({ setToken }: { setToken: (value: string) => void }) => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(); - const handleSubmit = (e: any) => { + const verifyToken = async () => { + axiosConfig.get("/GetAllInstallations"); + }; + + const handleSubmit = () => { setLoading(true); loginUser(username, password).then(({ data }) => { + // TODO change this if they return err codes from backend if (typeof data === "string") { - setToken(data); - setLoading(false); + verifyToken() + .then(() => { + setToken(data); + setLoading(false); + }) + .catch((err) => { + setError(err); + setLoading(false); + }); } setError(data); setLoading(false); diff --git a/typescript/Frontend/src/api.tsx b/typescript/Frontend/src/api.tsx deleted file mode 100644 index 7dfdd0de7..000000000 --- a/typescript/Frontend/src/api.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import axios from 'axios'; - -export const API = axios.create({ - baseURL: `https://localhost:7291/` -}); \ No newline at end of file diff --git a/typescript/Frontend/src/components/GroupTabs.tsx b/typescript/Frontend/src/components/GroupTabs.tsx deleted file mode 100644 index dee0b92dd..000000000 --- a/typescript/Frontend/src/components/GroupTabs.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from "react"; -import Tabs from "@mui/material/Tabs"; -import Tab from "@mui/material/Tab"; -import Box from "@mui/material/Box"; -import { Link } from "react-router-dom"; -import routes from "../routes.json"; -import useRouteMatch from "../hooks/useRouteMatch"; -import { useIntl } from "react-intl"; - -const GroupTabs = () => { - const routeMatch = useRouteMatch([ - routes.groups + routes.group + ":id", - routes.groups + routes.users + ":id", - ]); - - const id = routeMatch?.params?.id; - const intl = useIntl(); - - return ( - - - - - - - - - ); -}; - -export default GroupTabs; diff --git a/typescript/Frontend/src/components/GroupTree.tsx b/typescript/Frontend/src/components/GroupTree.tsx deleted file mode 100644 index cd400e398..000000000 --- a/typescript/Frontend/src/components/GroupTree.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import TreeView from "@mui/lab/TreeView"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import { ReactNode, useEffect, useState } from "react"; -import { TreeItem } from "@mui/lab"; -import { I_Folder, I_Installation } from "../util/types"; -import axiosConfig from "../config/axiosConfig"; -import { Link } from "react-router-dom"; -const GroupTree = () => { - const [data, setData] = useState<(I_Folder | I_Installation)[]>(); - - useEffect(() => { - axiosConfig.get("/GetTree").then((res) => { - setData(res.data); - }); - }, []); - - const instanceOfFolder = (object: any): object is I_Folder => { - return "children" in object; - }; - - const getNodes = (element: I_Folder | I_Installation): null | ReactNode => { - if (instanceOfFolder(element)) { - return element.children ? renderTree(element.children) : null; - } - return null; - }; - - const renderTree = (data: (I_Folder | I_Installation)[]): ReactNode => { - return data.map((element) => { - return ( - - - {getNodes(element)} - - - ); - }); - }; - - return ( - } - defaultExpandIcon={} - sx={{ height: 300, flexGrow: 1, maxWidth: 400 }} - > - {data && renderTree(data)} - - ); -}; - -export default GroupTree; diff --git a/typescript/Frontend/src/routes/Group.tsx b/typescript/Frontend/src/components/Groups/Folder.tsx similarity index 77% rename from typescript/Frontend/src/routes/Group.tsx rename to typescript/Frontend/src/components/Groups/Folder.tsx index fe1d53cde..3215d7564 100644 --- a/typescript/Frontend/src/routes/Group.tsx +++ b/typescript/Frontend/src/components/Groups/Folder.tsx @@ -2,10 +2,11 @@ import { Box, CircularProgress, Alert } from "@mui/material"; import { AxiosError } from "axios"; import { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; -import axiosConfig from "../config/axiosConfig"; -import { I_Installation } from "../util/types"; +import axiosConfig from "../../config/axiosConfig"; +import { I_Installation } from "../../util/types"; +import FolderForm from "./FolderForm"; -const Group = () => { +const Folder = () => { const { id } = useParams(); const [values, setValues] = useState(); const [loading, setLoading] = useState(false); @@ -26,7 +27,13 @@ const Group = () => { }, [id]); if (values && values.id && values.id.toString() === id) { - return {id}; + return ( + <> + + + + + ); } else if (loading) { return ( { return null; }; -export default Group; +export default Folder; diff --git a/typescript/Frontend/src/components/Groups/FolderForm.tsx b/typescript/Frontend/src/components/Groups/FolderForm.tsx new file mode 100644 index 000000000..2dac7aec6 --- /dev/null +++ b/typescript/Frontend/src/components/Groups/FolderForm.tsx @@ -0,0 +1,91 @@ +import { Alert, Button, Grid, Snackbar } from "@mui/material"; +import { useFormik } from "formik"; +import { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; +import axiosConfig from "../../config/axiosConfig"; +import { I_Folder } from "../../util/types"; +import InnovenergyTextfield from "../Layout/InnovenergyTextfield"; + +interface I_CustomerFormProps { + values: I_Folder; + id: string; +} + +const updateFolder = (data: I_Folder) => { + return axiosConfig.put("/UpdateFolder", data); +}; +const FolderForm = (props: I_CustomerFormProps) => { + const { values, id } = props; + const intl = useIntl(); + + const [snackbarOpen, setSnackbarOpen] = useState(false); + + const formik = useFormik({ + initialValues: { + name: values.name, + information: values.information, + }, + onSubmit: (formikValues) => { + const idAsNumber = parseInt(id, 10); + updateFolder({ + ...values, + ...formikValues, + id: idAsNumber, + }).then((res) => { + setSnackbarOpen(true); + }); + }, + }); + + const handleClose = () => { + setSnackbarOpen(false); + }; + + return ( +
+ + + + + + + + + + + + ); +}; + +export default FolderForm; diff --git a/typescript/Frontend/src/components/Groups/GroupTabs.tsx b/typescript/Frontend/src/components/Groups/GroupTabs.tsx new file mode 100644 index 000000000..06360665c --- /dev/null +++ b/typescript/Frontend/src/components/Groups/GroupTabs.tsx @@ -0,0 +1,61 @@ +import * as React from "react"; +import Tabs from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Box from "@mui/material/Box"; +import { Link } from "react-router-dom"; +import routes from "../../routes.json"; +import useRouteMatch from "../../hooks/useRouteMatch"; +import { useIntl } from "react-intl"; + +const GroupTabs = () => { + const routeMatch = useRouteMatch([ + routes.groups + routes.folder + ":id", + routes.groups + routes.users + ":id", + routes.groups + routes.installation + ":id", + ]); + + const id = routeMatch?.params?.id; + const intl = useIntl(); + + return ( + + + + {routeMatch?.pathname.includes("folder") ? ( + + ) : ( + + )} + + + + + + ); +}; + +export default GroupTabs; diff --git a/typescript/Frontend/src/routes/Groups.tsx b/typescript/Frontend/src/components/Groups/Groups.tsx similarity index 51% rename from typescript/Frontend/src/routes/Groups.tsx rename to typescript/Frontend/src/components/Groups/Groups.tsx index d5eb782c9..3fd71db92 100644 --- a/typescript/Frontend/src/routes/Groups.tsx +++ b/typescript/Frontend/src/components/Groups/Groups.tsx @@ -1,11 +1,12 @@ import { Grid } from "@mui/material"; import { Container } from "@mui/system"; import { Routes, Route } from "react-router"; -import GroupTabs from "../components/GroupTabs"; -import NavigationButtons from "../components/NavigationButtons"; -import UserTree from "../components/GroupTree"; -import routes from "../routes.json"; -import Group from "./Group"; +import routes from "../../routes.json"; +import InstallationDetail from "../Installations/Installation"; +import NavigationButtons from "../Layout/NavigationButtons"; +import Folder from "./Folder"; +import GroupTabs from "./GroupTabs"; +import GroupTree from "./Tree/GroupTree"; const Groups = () => { return ( @@ -13,13 +14,17 @@ const Groups = () => { - + - } index /> + } index /> Users} /> + } + /> diff --git a/typescript/Frontend/src/components/Groups/Tree/DragPreview.module.scss b/typescript/Frontend/src/components/Groups/Tree/DragPreview.module.scss new file mode 100644 index 000000000..54a9c3672 --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/DragPreview.module.scss @@ -0,0 +1,20 @@ +.root { + align-items: "center"; + background-color: #1967d2; + border-radius: 4px; + box-shadow: 0 12px 24px -6px rgba(0, 0, 0, 0.25), + 0 0 0 1px rgba(0, 0, 0, 0.08); + color: #fff; + display: inline-grid; + font-size: 14px; + gap: 8px; + grid-template-columns: auto auto; + padding: 4px 8px; + pointer-events: none; +} + +.icon, +.label { + align-items: center; + display: flex; +} diff --git a/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx b/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx new file mode 100644 index 000000000..a27d707a9 --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx @@ -0,0 +1,23 @@ +import { DragLayerMonitorProps } from "@minoru/react-dnd-treeview"; +import { I_Installation, I_Folder } from "../../../util/types"; +import styles from "./DragPreview.module.scss"; +import TypeIcon from "./TypeIcon"; + +interface DragPreviewProps { + monitorProps: DragLayerMonitorProps; +} + +const DragPreview = (props: DragPreviewProps) => { + const item = props.monitorProps.item; + + return ( +
+
+ +
+
{item.text}
+
+ ); +}; + +export default DragPreview; diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss b/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss new file mode 100644 index 000000000..11d791fb0 --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss @@ -0,0 +1,25 @@ +.tree { + list-style-type: none; + padding-left: 0; +} + +.app { + height: 100%; +} + +.treeRoot { + height: 100%; +} + +.draggingSource { + opacity: 0.3; +} + +.dropTarget { + background-color: #e8f0fe; +} + +.treeContainer { + height: 500px; + overflow: auto; +} diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx b/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx new file mode 100644 index 000000000..c5b9ffe9d --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx @@ -0,0 +1,116 @@ +import { useEffect, useState } from "react"; +import axiosConfig from "../../../config/axiosConfig"; +import { I_Folder, I_Installation } from "../../../util/types"; +import { CircularProgress, Grid } from "@mui/material"; +import { DndProvider } from "react-dnd"; +import { + MultiBackend, + getBackendOptions, + Tree, + NodeModel, + DropOptions, +} from "@minoru/react-dnd-treeview"; +import TreeNode from "./TreeNode"; +import styles from "./GroupTree.module.scss"; +import withScrolling from "react-dnd-scrolling"; +import DragPreview from "./DragPreview"; + +const getTreeData = ( + data: (I_Folder | I_Installation)[] +): NodeModel[] => { + return data.map((element) => { + const isFolder = element.type === "Folder"; + return { + id: isFolder ? element.id : "installation-" + element.id, + parent: element.parentId, + text: element.name, + droppable: isFolder, + data: element, + }; + }); +}; + +const GroupTree = () => { + const [data, setData] = useState<(I_Folder | I_Installation)[]>(); + const [loading, setLoading] = useState(false); + const ScrollingComponent = withScrolling("div"); + + useEffect(() => { + getData(); + }, []); + + const getData = async () => { + setLoading(true); + return axiosConfig.get("/GetAllFoldersAndInstallations").then((res) => { + setData(res.data); + setLoading(false); + }); + }; + + const handleDrop = ( + newTree: NodeModel[], + { dropTargetId, dragSource }: DropOptions + ) => { + axiosConfig + .put( + dragSource?.data?.type === "Folder" + ? "/UpdateFolder" + : "/UpdateInstallation", + { + ...dragSource?.data, + parentId: dropTargetId, + } + ) + .then(() => { + getData(); + }); + }; + + if (loading) { + return ( + + + + ); + } else if (data && data?.length > 1) { + return ( + + + + tree={getTreeData(data)} + rootId={0} + dragPreviewRender={(monitorProps) => ( + + )} + classes={{ + container: styles.tree, + root: styles.treeRoot, + draggingSource: styles.draggingSource, + dropTarget: styles.dropTarget, + }} + render={( + node: NodeModel, + { depth, isOpen, onToggle, hasChild, handleRef } + ) => ( + + )} + onDrop={( + tree: NodeModel[], + options: DropOptions + ) => handleDrop(tree, options)} + /> + + + ); + } + return null; +}; + +export default GroupTree; diff --git a/typescript/Frontend/src/components/Groups/Tree/TreeNode.module.scss b/typescript/Frontend/src/components/Groups/Tree/TreeNode.module.scss new file mode 100644 index 000000000..e36b7be8c --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/TreeNode.module.scss @@ -0,0 +1,36 @@ +.root { + align-items: center; + display: grid; + grid-template-columns: auto auto 1fr auto; + height: 32px; + padding-inline-end: 8px; +} + +.expandIconWrapper { + align-items: center; + font-size: 0; + cursor: pointer; + display: flex; + height: 24px; + justify-content: center; + width: 24px; + transition: transform linear 0.1s; + transform: rotate(0deg); +} + +.expandIconWrapper.isOpen { + transform: rotate(90deg); +} + +.labelGridItem { + padding-inline-start: 8px; +} + +.handle { + cursor: grab; + display: flex; +} + +.handle > svg { + pointer-events: none; +} diff --git a/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx b/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx new file mode 100644 index 000000000..3080cab71 --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import Typography from "@mui/material/Typography"; +import ArrowRightIcon from "@mui/icons-material/ArrowRight"; +import { NodeModel } from "@minoru/react-dnd-treeview"; +import styles from "./TreeNode.module.scss"; +import { Link } from "react-router-dom"; +import routes from "../../../routes.json"; +import { I_Folder, I_Installation } from "../../../util/types"; +import TypeIcon from "./TypeIcon"; +import DragHandleIcon from "@mui/icons-material/DragHandle"; + +type Props = { + node: NodeModel; + depth: number; + isOpen: boolean; + onToggle: (id: NodeModel["id"]) => void; + hasChild: boolean; + handleRef: React.RefObject; +}; + +const TreeNode: React.FC = (props) => { + const { node, isOpen, hasChild, onToggle, depth, handleRef } = props; + const indent = depth * 24; + + const handleToggle = (e: React.MouseEvent) => { + e.stopPropagation(); + onToggle(node.id); + }; + + return ( +
+
+ {node.droppable && hasChild && ( +
+ +
+ )} +
+ + +
+ {node.text} +
+ +
+ +
+
+ ); +}; + +export default TreeNode; diff --git a/typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx b/typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx new file mode 100644 index 000000000..c86b4afd7 --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx @@ -0,0 +1,15 @@ +import FolderIcon from "@mui/icons-material/Folder"; +import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; + +interface TypeIconProps { + type: string | undefined; +} +const TypeIcon = (props: TypeIconProps) => { + return ( +
+ {props.type === "Folder" ? : } +
+ ); +}; + +export default TypeIcon; diff --git a/typescript/Frontend/src/routes/Alarms.tsx b/typescript/Frontend/src/components/Installations/Alarms.tsx similarity index 93% rename from typescript/Frontend/src/routes/Alarms.tsx rename to typescript/Frontend/src/components/Installations/Alarms.tsx index a7cb1420d..9baf6bf70 100644 --- a/typescript/Frontend/src/routes/Alarms.tsx +++ b/typescript/Frontend/src/components/Installations/Alarms.tsx @@ -1,5 +1,5 @@ -const Alarms = () => { - return
alarms
; -}; - -export default Alarms; +const Alarms = () => { + return
alarms
; +}; + +export default Alarms; diff --git a/typescript/Frontend/src/components/CustomerForm.tsx b/typescript/Frontend/src/components/Installations/CustomerForm.tsx similarity index 90% rename from typescript/Frontend/src/components/CustomerForm.tsx rename to typescript/Frontend/src/components/Installations/CustomerForm.tsx index a5393c71e..f37718c95 100644 --- a/typescript/Frontend/src/components/CustomerForm.tsx +++ b/typescript/Frontend/src/components/Installations/CustomerForm.tsx @@ -1,120 +1,120 @@ -import { Alert, Button, Grid, Snackbar } from "@mui/material"; -import { useFormik } from "formik"; -import { useState } from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import axiosConfig from "../config/axiosConfig"; -import { I_Installation } from "../util/types"; -import InnovenergyTextfield from "./InnovenergyTextfield"; - -interface I_CustomerFormProps { - values: I_Installation; - id: string | undefined; -} -const CustomerForm = (props: I_CustomerFormProps) => { - const { values, id } = props; - const intl = useIntl(); - - const [open, setOpen] = useState(false); - - const formik = useFormik({ - initialValues: { - name: values.name, - region: values.region, - location: values.location, - country: values.country, - orderNumbers: values.orderNumbers, - }, - onSubmit: (formikValues) => { - axiosConfig - .put("https://localhost:7087/api/UpdateInstallation", { - ...formikValues, - id, - }) - .then((res) => { - setOpen(true); - }); - }, - }); - - const handleClose = () => { - setOpen(false); - }; - - return ( -
- - - - - - - - - - - - - - - ); -}; - -export default CustomerForm; +import { Alert, Button, Grid, Snackbar } from "@mui/material"; +import { useFormik } from "formik"; +import { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; +import axiosConfig from "../../config/axiosConfig"; +import { I_Installation } from "../../util/types"; +import InnovenergyTextfield from "../Layout/InnovenergyTextfield"; + +interface I_CustomerFormProps { + values: I_Installation; + id: string | undefined; +} +const CustomerForm = (props: I_CustomerFormProps) => { + const { values, id } = props; + const intl = useIntl(); + + const [open, setOpen] = useState(false); + + const formik = useFormik({ + initialValues: { + name: values.name, + region: values.region, + location: values.location, + country: values.country, + orderNumbers: values.orderNumbers, + }, + onSubmit: (formikValues) => { + axiosConfig + .put("/UpdateInstallation", { + ...formikValues, + id, + }) + .then((res) => { + setOpen(true); + }); + }, + }); + + const handleClose = () => { + setOpen(false); + }; + + return ( +
+ + + + + + + + + + + + + + + ); +}; + +export default CustomerForm; diff --git a/typescript/Frontend/src/routes/Installation.tsx b/typescript/Frontend/src/components/Installations/Installation.tsx similarity index 86% rename from typescript/Frontend/src/routes/Installation.tsx rename to typescript/Frontend/src/components/Installations/Installation.tsx index fe6c60dac..de6f78824 100644 --- a/typescript/Frontend/src/routes/Installation.tsx +++ b/typescript/Frontend/src/components/Installations/Installation.tsx @@ -1,53 +1,53 @@ -import { Alert, Box, CircularProgress } from "@mui/material"; -import { AxiosError } from "axios"; -import { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; -import CustomerForm from "../components/CustomerForm"; -import axiosConfig from "../config/axiosConfig"; -import { I_Installation } from "../util/types"; - -const InstallationDetail = () => { - const { id } = useParams(); - const [values, setValues] = useState(); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(); - - useEffect(() => { - setLoading(true); - axiosConfig - .get("/GetInstallationById?id=" + id) - .then((res) => { - setValues(res.data); - setLoading(false); - }) - .catch((err: AxiosError) => { - setError(err); - setLoading(false); - }); - }, [id]); - - if (values && values.id && values.id.toString() === id) { - return ( - - - - ); - } else if (loading) { - return ( - - - - ); - } else if (error) { - return ( - - {error.message} - - ); - } - return null; -}; - -export default InstallationDetail; +import { Alert, Box, CircularProgress } from "@mui/material"; +import { AxiosError } from "axios"; +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import CustomerForm from "./CustomerForm"; +import axiosConfig from "../../config/axiosConfig"; +import { I_Installation } from "../../util/types"; + +const InstallationDetail = () => { + const { id } = useParams(); + const [values, setValues] = useState(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + + useEffect(() => { + setLoading(true); + axiosConfig + .get("/GetInstallationById?id=" + id) + .then((res) => { + setValues(res.data); + setLoading(false); + }) + .catch((err: AxiosError) => { + setError(err); + setLoading(false); + }); + }, [id]); + + if (values && values.id && values.id.toString() === id) { + return ( + + + + ); + } else if (loading) { + return ( + + + + ); + } else if (error) { + return ( + + {error.message} + + ); + } + return null; +}; + +export default InstallationDetail; diff --git a/typescript/Frontend/src/components/InstallationList.tsx b/typescript/Frontend/src/components/Installations/InstallationList.tsx similarity index 93% rename from typescript/Frontend/src/components/InstallationList.tsx rename to typescript/Frontend/src/components/Installations/InstallationList.tsx index 082202e7c..b145cd0b3 100644 --- a/typescript/Frontend/src/components/InstallationList.tsx +++ b/typescript/Frontend/src/components/Installations/InstallationList.tsx @@ -3,11 +3,11 @@ import ListItemButton from "@mui/material/ListItemButton"; import ListItemText from "@mui/material/ListItemText"; import { CircularProgress, Divider, Grid } from "@mui/material"; import { Link } from "react-router-dom"; -import useRouteMatch from "../hooks/useRouteMatch"; -import routes from "../routes.json"; +import useRouteMatch from "../../hooks/useRouteMatch"; +import routes from "../../routes.json"; import { Fragment, useEffect, useState } from "react"; -import { I_Installation } from "../util/types"; -import axiosConfig from "../config/axiosConfig"; +import { I_Installation } from "../../util/types"; +import axiosConfig from "../../config/axiosConfig"; interface InstallationListProps { searchQuery: string; diff --git a/typescript/Frontend/src/components/InstallationTabs.tsx b/typescript/Frontend/src/components/Installations/InstallationTabs.tsx similarity index 95% rename from typescript/Frontend/src/components/InstallationTabs.tsx rename to typescript/Frontend/src/components/Installations/InstallationTabs.tsx index 678518c84..1b56e574d 100644 --- a/typescript/Frontend/src/components/InstallationTabs.tsx +++ b/typescript/Frontend/src/components/Installations/InstallationTabs.tsx @@ -3,8 +3,8 @@ import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import Box from "@mui/material/Box"; import { Link } from "react-router-dom"; -import routes from "../routes.json"; -import useRouteMatch from "../hooks/useRouteMatch"; +import routes from "../../routes.json"; +import useRouteMatch from "../../hooks/useRouteMatch"; import { useIntl } from "react-intl"; const InstallationTabs = () => { diff --git a/typescript/Frontend/src/routes/Installations.tsx b/typescript/Frontend/src/components/Installations/Installations.tsx similarity index 79% rename from typescript/Frontend/src/routes/Installations.tsx rename to typescript/Frontend/src/components/Installations/Installations.tsx index 2670ce302..5a3086918 100644 --- a/typescript/Frontend/src/routes/Installations.tsx +++ b/typescript/Frontend/src/components/Installations/Installations.tsx @@ -1,14 +1,14 @@ import { Grid } from "@mui/material"; import { Container } from "@mui/system"; import { Routes, Route } from "react-router"; -import InstallationTabs from "../components/InstallationTabs"; -import NavigationButtons from "../components/NavigationButtons"; -import BasicTable from "../components/Table"; +import NavigationButtons from "../Layout/NavigationButtons"; +import Sidebar from "../Layout/Sidebar"; +import BasicTable from "../Layout/Table"; import Alarms from "./Alarms"; import InstallationDetail from "./Installation"; +import InstallationTabs from "./InstallationTabs"; import Log from "./Log"; -import routes from "../routes.json"; -import Sidebar from "../components/Sidebar"; +import routes from "../../routes.json"; const Installations = () => { return ( diff --git a/typescript/Frontend/src/routes/Log.tsx b/typescript/Frontend/src/components/Installations/Log.tsx similarity index 95% rename from typescript/Frontend/src/routes/Log.tsx rename to typescript/Frontend/src/components/Installations/Log.tsx index 8b1eb817a..c3606b476 100644 --- a/typescript/Frontend/src/routes/Log.tsx +++ b/typescript/Frontend/src/components/Installations/Log.tsx @@ -1,156 +1,156 @@ -const Log = () => { - const foo = { - TimeStamp: "1676643900", - Devices: [ - { - "TruConvertAc 205330741": { - Ac: { - L1: { - Current: 2.49, - Voltage: 239.4, - Phi: 0.200334842323119592691046359, - }, - L2: { - Current: 2.65, - Voltage: 239.6, - Phi: 0.200334842323119592691046359, - }, - L3: { - Current: 2.63, - Voltage: 239.8, - Phi: 0, - }, - Frequency: 49.98, - }, - Dc: { - Current: 2.249388753056234718826405868, - Voltage: 818, - Power: 1840.0, - }, - Alarms: [], - }, - }, - { - "TruConvertDc 3214": { - Dc: { - Current: -2.1173594132029339853300733496, - Voltage: 818, - Power: -1732.0, - }, - Dc48: { - Current: -30, - Voltage: 56.0, - Power: -1680.0, - }, - Warnings: [], - Alarms: [], - }, - }, - { - "EmuMeter 123": { - Ac: { - L1: { - Current: 3.212, - Voltage: 239.4, - Phi: 1.1483422646081408626645746948, - }, - L2: { - Current: -2.462, - Voltage: 238.9, - Phi: 1.8441893582623698418074097834, - }, - L3: { - Current: 2.995, - Voltage: 238.8, - Phi: 0.1415394733244272187457893568, - }, - Frequency: 49.9, - }, - }, - }, - { - "EmuMeter 123": { - Ac: { - L1: { - Current: 3.212, - Voltage: 239.4, - Phi: 1.1483422646081408626645746948, - }, - L2: { - Current: -2.462, - Voltage: 238.9, - Phi: 1.8441893582623698418074097834, - }, - L3: { - Current: 2.995, - Voltage: 238.8, - Phi: 0.1415394733244272187457893568, - }, - Frequency: 49.9, - }, - }, - }, - { - Name: "AMPT", - Type: "PvOnDc", - "Current 1": 2.098, - "Current 2": 2.575, - "Voltage 1": 822.989, - "Voltage 2": 823.169, - "Power 1": 1726.630922, - "Power 2": 2119.660175, - }, - { - Name: "48TL Battery", - Type: "Battery", - Dc48: { - Current: 14.17, - Voltage: 53.41, - Power: 756.8197, - }, - Alarms: [], - Warnings: [], - Soc: 77.4, - HeaterOn: true, - EocReached: false, - BatteryCold: false, - Temperature: 265.4, - }, - { - Name: "48TL Battery", - Type: "Battery", - Dc48: { - Current: 11.3, - Voltage: 53.4, - Power: 603.42, - }, - Alarms: [], - Warnings: ["bit44:"], - Soc: 77.6, - HeaterOn: true, - EocReached: false, - BatteryCold: false, - Temperature: 264.9, - }, - ], - }; - - const flattenObject = (obj: any, prefix = "") => - Object.keys(obj).reduce((previous: any, current: any) => { - const pre = prefix.length ? prefix + "/" : ""; - if (Array.isArray(obj) || typeof obj === "string") { - return previous; - } - if (typeof obj[current] === "object") { - Object.assign(previous, flattenObject(obj[current], pre + current)); - } else { - previous[pre + current] = obj[current]; - } - return previous; - }, {}); - - console.log(flattenObject(foo.Devices[0])); - return
log
; -}; - -export default Log; +const Log = () => { + const foo = { + TimeStamp: "1676643900", + Devices: [ + { + "TruConvertAc 205330741": { + Ac: { + L1: { + Current: 2.49, + Voltage: 239.4, + Phi: 0.200334842323119592691046359, + }, + L2: { + Current: 2.65, + Voltage: 239.6, + Phi: 0.200334842323119592691046359, + }, + L3: { + Current: 2.63, + Voltage: 239.8, + Phi: 0, + }, + Frequency: 49.98, + }, + Dc: { + Current: 2.249388753056234718826405868, + Voltage: 818, + Power: 1840.0, + }, + Alarms: [], + }, + }, + { + "TruConvertDc 3214": { + Dc: { + Current: -2.1173594132029339853300733496, + Voltage: 818, + Power: -1732.0, + }, + Dc48: { + Current: -30, + Voltage: 56.0, + Power: -1680.0, + }, + Warnings: [], + Alarms: [], + }, + }, + { + "EmuMeter 123": { + Ac: { + L1: { + Current: 3.212, + Voltage: 239.4, + Phi: 1.1483422646081408626645746948, + }, + L2: { + Current: -2.462, + Voltage: 238.9, + Phi: 1.8441893582623698418074097834, + }, + L3: { + Current: 2.995, + Voltage: 238.8, + Phi: 0.1415394733244272187457893568, + }, + Frequency: 49.9, + }, + }, + }, + { + "EmuMeter 123": { + Ac: { + L1: { + Current: 3.212, + Voltage: 239.4, + Phi: 1.1483422646081408626645746948, + }, + L2: { + Current: -2.462, + Voltage: 238.9, + Phi: 1.8441893582623698418074097834, + }, + L3: { + Current: 2.995, + Voltage: 238.8, + Phi: 0.1415394733244272187457893568, + }, + Frequency: 49.9, + }, + }, + }, + { + Name: "AMPT", + Type: "PvOnDc", + "Current 1": 2.098, + "Current 2": 2.575, + "Voltage 1": 822.989, + "Voltage 2": 823.169, + "Power 1": 1726.630922, + "Power 2": 2119.660175, + }, + { + Name: "48TL Battery", + Type: "Battery", + Dc48: { + Current: 14.17, + Voltage: 53.41, + Power: 756.8197, + }, + Alarms: [], + Warnings: [], + Soc: 77.4, + HeaterOn: true, + EocReached: false, + BatteryCold: false, + Temperature: 265.4, + }, + { + Name: "48TL Battery", + Type: "Battery", + Dc48: { + Current: 11.3, + Voltage: 53.4, + Power: 603.42, + }, + Alarms: [], + Warnings: ["bit44:"], + Soc: 77.6, + HeaterOn: true, + EocReached: false, + BatteryCold: false, + Temperature: 264.9, + }, + ], + }; + + const flattenObject = (obj: any, prefix = "") => + Object.keys(obj).reduce((previous: any, current: any) => { + const pre = prefix.length ? prefix + "/" : ""; + if (Array.isArray(obj) || typeof obj === "string") { + return previous; + } + if (typeof obj[current] === "object") { + Object.assign(previous, flattenObject(obj[current], pre + current)); + } else { + previous[pre + current] = obj[current]; + } + return previous; + }, {}); + + console.log(flattenObject(foo.Devices[0])); + return
log
; +}; + +export default Log; diff --git a/typescript/Frontend/src/components/FlowComp.tsx b/typescript/Frontend/src/components/Layout/FlowComp.tsx similarity index 95% rename from typescript/Frontend/src/components/FlowComp.tsx rename to typescript/Frontend/src/components/Layout/FlowComp.tsx index 9868366f2..ef40a9ad9 100644 --- a/typescript/Frontend/src/components/FlowComp.tsx +++ b/typescript/Frontend/src/components/Layout/FlowComp.tsx @@ -1,57 +1,57 @@ -import { useState, useCallback } from "react"; -import ReactFlow, { - addEdge, - FitViewOptions, - applyNodeChanges, - applyEdgeChanges, - Node, - Edge, - NodeChange, - EdgeChange, - Connection, -} from "reactflow"; - -const initialNodes: Node[] = [ - { id: "1", data: { label: "Node 1" }, position: { x: 5, y: 5 } }, - { id: "2", data: { label: "Node 2" }, position: { x: 5, y: 100 } }, -]; - -const initialEdges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }]; - -const fitViewOptions: FitViewOptions = { - padding: 0.2, -}; - -function Flow() { - const [nodes, setNodes] = useState(initialNodes); - const [edges, setEdges] = useState(initialEdges); - - const onNodesChange = useCallback( - (changes: NodeChange[]) => - setNodes((nds) => applyNodeChanges(changes, nds)), - [setNodes] - ); - const onEdgesChange = useCallback( - (changes: EdgeChange[]) => - setEdges((eds) => applyEdgeChanges(changes, eds)), - [setEdges] - ); - const onConnect = useCallback( - (connection: Connection) => setEdges((eds) => addEdge(connection, eds)), - [setEdges] - ); - - return ( - - ); -} - -export default Flow; +import { useState, useCallback } from "react"; +import ReactFlow, { + addEdge, + FitViewOptions, + applyNodeChanges, + applyEdgeChanges, + Node, + Edge, + NodeChange, + EdgeChange, + Connection, +} from "reactflow"; + +const initialNodes: Node[] = [ + { id: "1", data: { label: "Node 1" }, position: { x: 5, y: 5 } }, + { id: "2", data: { label: "Node 2" }, position: { x: 5, y: 100 } }, +]; + +const initialEdges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }]; + +const fitViewOptions: FitViewOptions = { + padding: 0.2, +}; + +function Flow() { + const [nodes, setNodes] = useState(initialNodes); + const [edges, setEdges] = useState(initialEdges); + + const onNodesChange = useCallback( + (changes: NodeChange[]) => + setNodes((nds) => applyNodeChanges(changes, nds)), + [setNodes] + ); + const onEdgesChange = useCallback( + (changes: EdgeChange[]) => + setEdges((eds) => applyEdgeChanges(changes, eds)), + [setEdges] + ); + const onConnect = useCallback( + (connection: Connection) => setEdges((eds) => addEdge(connection, eds)), + [setEdges] + ); + + return ( + + ); +} + +export default Flow; diff --git a/typescript/Frontend/src/components/InnovenergyTextfield.tsx b/typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx similarity index 96% rename from typescript/Frontend/src/components/InnovenergyTextfield.tsx rename to typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx index 0bcd5b57e..055cadd0c 100644 --- a/typescript/Frontend/src/components/InnovenergyTextfield.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx @@ -1,38 +1,38 @@ -import { Grid, InputLabel, TextField } from "@mui/material"; - -interface I_InnovenergyTextfieldProps { - id: string; - label: string; - value: string; - name: string; - handleChange: (e: React.ChangeEvent) => void; - type?: string; - readOnly?: boolean; -} - -const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => { - return ( - - - {props.label} - - - - - - ); -}; - -export default InnovenergyTextfield; +import { Grid, InputLabel, TextField } from "@mui/material"; + +interface I_InnovenergyTextfieldProps { + id: string; + label: string; + value: string; + name: string; + handleChange: (e: React.ChangeEvent) => void; + type?: string; + readOnly?: boolean; +} + +const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => { + return ( + + + {props.label} + + + + + + ); +}; + +export default InnovenergyTextfield; diff --git a/typescript/Frontend/src/components/LanguageSelect.tsx b/typescript/Frontend/src/components/Layout/LanguageSelect.tsx similarity index 100% rename from typescript/Frontend/src/components/LanguageSelect.tsx rename to typescript/Frontend/src/components/Layout/LanguageSelect.tsx diff --git a/typescript/Frontend/src/components/LogoutButton.tsx b/typescript/Frontend/src/components/Layout/LogoutButton.tsx similarity index 92% rename from typescript/Frontend/src/components/LogoutButton.tsx rename to typescript/Frontend/src/components/Layout/LogoutButton.tsx index fe773519e..bb1ab45ad 100644 --- a/typescript/Frontend/src/components/LogoutButton.tsx +++ b/typescript/Frontend/src/components/Layout/LogoutButton.tsx @@ -1,7 +1,7 @@ import { Button } from "@mui/material"; import { FormattedMessage } from "react-intl"; -import axiosConfig from "../config/axiosConfig"; import { useNavigate } from "react-router-dom"; +import axiosConfig from "../../config/axiosConfig"; interface LogoutButtonProps { removeToken: () => void; diff --git a/typescript/Frontend/src/components/NavigationButtons.tsx b/typescript/Frontend/src/components/Layout/NavigationButtons.tsx similarity index 80% rename from typescript/Frontend/src/components/NavigationButtons.tsx rename to typescript/Frontend/src/components/Layout/NavigationButtons.tsx index 0283785e2..66c53a8e8 100644 --- a/typescript/Frontend/src/components/NavigationButtons.tsx +++ b/typescript/Frontend/src/components/Layout/NavigationButtons.tsx @@ -1,8 +1,8 @@ import { ToggleButton, ToggleButtonGroup } from "@mui/material"; import { FormattedMessage } from "react-intl"; import { Link } from "react-router-dom"; -import useRouteMatch from "../hooks/useRouteMatch"; -import routes from "../routes.json"; +import useRouteMatch from "../../hooks/useRouteMatch"; +import routes from "../../routes.json"; const NavigationButtons = () => { const routeMatch = useRouteMatch([ @@ -27,7 +27,11 @@ const NavigationButtons = () => { defaultMessage="All installations" /> - + diff --git a/typescript/Frontend/src/components/Sidebar.tsx b/typescript/Frontend/src/components/Layout/Sidebar.tsx similarity index 90% rename from typescript/Frontend/src/components/Sidebar.tsx rename to typescript/Frontend/src/components/Layout/Sidebar.tsx index 00d97c9a3..35c666f54 100644 --- a/typescript/Frontend/src/components/Sidebar.tsx +++ b/typescript/Frontend/src/components/Layout/Sidebar.tsx @@ -1,7 +1,7 @@ import { TextField } from "@mui/material"; import { useState } from "react"; import { useIntl } from "react-intl"; -import InstallationList from "./InstallationList"; +import InstallationList from "../Installations/InstallationList"; const Sidebar = () => { const [searchQuery, setSearchQuery] = useState(""); diff --git a/typescript/Frontend/src/components/Table.tsx b/typescript/Frontend/src/components/Layout/Table.tsx similarity index 96% rename from typescript/Frontend/src/components/Table.tsx rename to typescript/Frontend/src/components/Layout/Table.tsx index c454f0989..1c458b997 100644 --- a/typescript/Frontend/src/components/Table.tsx +++ b/typescript/Frontend/src/components/Layout/Table.tsx @@ -1,65 +1,65 @@ -import * as React from "react"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; -import TableRow from "@mui/material/TableRow"; -import Paper from "@mui/material/Paper"; - -const rows = [ - { - device: "48TL200", - code: "MSWE", - message: "Main switch error", - severity: "Alarm", - }, - { - device: "48TL200", - code: "MSWE", - message: "Main switch error", - severity: "Alarm", - }, - { - device: "48TL200", - code: "MSWE", - message: "Main switch error", - severity: "Alarm", - }, - { - device: "48TL200", - code: "MSWE", - message: "Main switch error", - severity: "Alarm", - }, -]; -const BasicTable = () => { - return ( - - - - - Device - Code - Message - Severity - - - - {rows.map((row, i) => ( - - {row.device} - {row.code} - {row.message} - {row.severity} - - ))} - -
-
- ); -}; - -export default BasicTable; +import * as React from "react"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; + +const rows = [ + { + device: "48TL200", + code: "MSWE", + message: "Main switch error", + severity: "Alarm", + }, + { + device: "48TL200", + code: "MSWE", + message: "Main switch error", + severity: "Alarm", + }, + { + device: "48TL200", + code: "MSWE", + message: "Main switch error", + severity: "Alarm", + }, + { + device: "48TL200", + code: "MSWE", + message: "Main switch error", + severity: "Alarm", + }, +]; +const BasicTable = () => { + return ( + + + + + Device + Code + Message + Severity + + + + {rows.map((row, i) => ( + + {row.device} + {row.code} + {row.message} + {row.severity} + + ))} + +
+
+ ); +}; + +export default BasicTable; diff --git a/typescript/Frontend/src/components/Navigation.scss b/typescript/Frontend/src/components/Navigation.scss deleted file mode 100644 index 75963a6b1..000000000 --- a/typescript/Frontend/src/components/Navigation.scss +++ /dev/null @@ -1,3 +0,0 @@ -.link { - text-decoration: none; -} \ No newline at end of file diff --git a/typescript/Frontend/src/components/Navigation.tsx b/typescript/Frontend/src/components/Navigation.tsx deleted file mode 100644 index 0ab872342..000000000 --- a/typescript/Frontend/src/components/Navigation.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import { Link } from 'react-router-dom'; - - -function Navigation() { - return ( - - - All installations - Users - - - ); -} -export default Navigation; diff --git a/typescript/Frontend/src/lang/de.json b/typescript/Frontend/src/lang/de.json index 6af37ed1d..80d807f0f 100644 --- a/typescript/Frontend/src/lang/de.json +++ b/typescript/Frontend/src/lang/de.json @@ -16,5 +16,6 @@ "logout": "Logout", "updatedSuccessfully": "Erfolgreich aktualisiert", "groups": "Gruppen", - "group": "Gruppe" + "group": "Gruppe", + "folder": "Ordner" } diff --git a/typescript/Frontend/src/lang/en.json b/typescript/Frontend/src/lang/en.json index a0d76620b..856e907aa 100644 --- a/typescript/Frontend/src/lang/en.json +++ b/typescript/Frontend/src/lang/en.json @@ -16,5 +16,7 @@ "logout": "Logout", "updatedSuccessfully": "Updated successfully", "groups": "Groups", - "group": "Group" + "group": "Group", + "folder": "folder" + } diff --git a/typescript/Frontend/src/routes.json b/typescript/Frontend/src/routes.json index 0aba4fef1..619e97e29 100644 --- a/typescript/Frontend/src/routes.json +++ b/typescript/Frontend/src/routes.json @@ -5,5 +5,6 @@ "log": "log/", "installations": "/installations/", "groups": "/groups/", - "group": "group/" + "group": "group/", + "folder": "folder/" }