From 5dffa1b77b2ff21d23183d2f81f006ea7b6b3a92 Mon Sep 17 00:00:00 2001 From: Eshaan Aggarwal <96648934+EshaanAgg@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:47:55 +0530 Subject: [PATCH] Add Feedback UI Components to Talawa Admin (#980) * Add feedback component * Add tests for feedback modal * Add handling for empty feedback * Add Average Rating and Reviews component * Add testing for all the added cards * Fix tests and move to 100% coverage * Add bugfix * Add merge function to fix failing tests * Add key definitons * Change merge policy * Add custom merge policy to all the Event Stat tests * remove cache * Migrate to a single query in the parent --- package-lock.json | 319 ++++++++++++++---- package.json | 5 +- src/App.test.tsx | 8 + src/GraphQl/Queries/Queries.ts | 14 + src/components/EventStats/EventStats.test.tsx | 62 ++++ src/components/EventStats/EventStats.tsx | 59 ++++ .../EventStats/EventStatsWrapper.test.tsx | 76 +++++ .../EventStats/EventStatsWrapper.tsx | 34 ++ src/components/EventStats/Loader.module.css | 43 +++ .../Statistics/AverageRating.test.tsx | 58 ++++ .../EventStats/Statistics/AverageRating.tsx | 60 ++++ .../EventStats/Statistics/Feedback.test.tsx | 100 ++++++ .../EventStats/Statistics/Feedback.tsx | 107 ++++++ .../EventStats/Statistics/Review.test.tsx | 95 ++++++ .../EventStats/Statistics/Review.tsx | 57 ++++ .../EventDashboard/EventDashboard.test.tsx | 8 + src/screens/EventDashboard/EventDashboard.tsx | 4 +- 17 files changed, 1048 insertions(+), 61 deletions(-) create mode 100644 src/components/EventStats/EventStats.test.tsx create mode 100644 src/components/EventStats/EventStats.tsx create mode 100644 src/components/EventStats/EventStatsWrapper.test.tsx create mode 100644 src/components/EventStats/EventStatsWrapper.tsx create mode 100644 src/components/EventStats/Loader.module.css create mode 100644 src/components/EventStats/Statistics/AverageRating.test.tsx create mode 100644 src/components/EventStats/Statistics/AverageRating.tsx create mode 100644 src/components/EventStats/Statistics/Feedback.test.tsx create mode 100644 src/components/EventStats/Statistics/Feedback.tsx create mode 100644 src/components/EventStats/Statistics/Review.test.tsx create mode 100644 src/components/EventStats/Statistics/Review.tsx diff --git a/package-lock.json b/package-lock.json index f2cf6800e6..18b767ad9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,11 @@ "@apollo/client": "^3.4.0-beta.19", "@apollo/link-error": "^2.0.0-beta.3", "@apollo/react-testing": "^4.0.0", - "@emotion/react": "^11.11.0", + "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.8.3", - "@mui/material": "^5.13.3", + "@mui/material": "^5.14.1", + "@mui/x-charts": "^6.0.0-alpha.13", "@mui/x-data-grid": "^6.8.0", "@mui/x-date-pickers": "^6.6.0", "@pdfme/generator": "^1.2.6", @@ -2188,11 +2189,11 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -2210,6 +2211,11 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "node_modules/@babel/template": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", @@ -2773,6 +2779,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "0.2.36", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", @@ -3304,18 +3344,17 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, "node_modules/@mui/base": { - "version": "5.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.7.tgz", - "integrity": "sha512-Pjbwm6gjiS96kOMF7E5fjEJsenc0tZBesrLQ4rrdi3eT/c/yhSWnPbCUkHSz8bnS0l3/VQ8bA+oERSGSV2PK6A==", + "version": "5.0.0-beta.17", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.17.tgz", + "integrity": "sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@emotion/is-prop-valid": "^1.2.1", + "@babel/runtime": "^7.22.15", + "@floating-ui/react-dom": "^2.0.2", "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.7", + "@mui/utils": "^5.14.11", "@popperjs/core": "^2.11.8", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "clsx": "^2.0.0", + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" @@ -3335,10 +3374,18 @@ } } }, + "node_modules/@mui/base/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.0.tgz", - "integrity": "sha512-SYBOVCatVDUf/lbrLGah09bHhX5WfUXg7kSskfLILr6SvKRni0NLp0aonxQ0SMALVVK3Qwa6cW4CdWuwS0gC1w==", + "version": "5.14.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.11.tgz", + "integrity": "sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" @@ -3370,18 +3417,18 @@ } }, "node_modules/@mui/material": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.0.tgz", - "integrity": "sha512-HP7CP71NhMkui2HUIEKl2/JfuHMuoarSUWAKlNw6s17bl/Num9rN61EM6uUzc2A2zHjj/00A66GnvDnmixEJEw==", - "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/base": "5.0.0-beta.7", - "@mui/core-downloads-tracker": "^5.14.0", - "@mui/system": "^5.14.0", + "version": "5.14.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.11.tgz", + "integrity": "sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==", + "dependencies": { + "@babel/runtime": "^7.22.15", + "@mui/base": "5.0.0-beta.17", + "@mui/core-downloads-tracker": "^5.14.11", + "@mui/system": "^5.14.11", "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.7", + "@mui/utils": "^5.14.11", "@types/react-transition-group": "^4.4.6", - "clsx": "^1.2.1", + "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", @@ -3413,13 +3460,21 @@ } } }, + "node_modules/@mui/material/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/private-theming": { - "version": "5.13.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.7.tgz", - "integrity": "sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==", + "version": "5.14.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.11.tgz", + "integrity": "sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/utils": "^5.13.7", + "@babel/runtime": "^7.22.15", + "@mui/utils": "^5.14.11", "prop-types": "^15.8.1" }, "engines": { @@ -3440,11 +3495,11 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", - "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "version": "5.14.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.11.tgz", + "integrity": "sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==", "dependencies": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.22.15", "@emotion/cache": "^11.11.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -3471,16 +3526,16 @@ } }, "node_modules/@mui/system": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.0.tgz", - "integrity": "sha512-0HZGkX8miJbiNw+rjlZ9l0Cfkz1bSqfSHQH0EH9J+nx0aAm5cBleg9piOlLdCNIWGgecCqsw4x62erGrGjjcJg==", + "version": "5.14.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.11.tgz", + "integrity": "sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/private-theming": "^5.13.7", - "@mui/styled-engine": "^5.13.2", + "@babel/runtime": "^7.22.15", + "@mui/private-theming": "^5.14.11", + "@mui/styled-engine": "^5.14.11", "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.7", - "clsx": "^1.2.1", + "@mui/utils": "^5.14.11", + "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" }, @@ -3509,6 +3564,14 @@ } } }, + "node_modules/@mui/system/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/types": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", @@ -3523,13 +3586,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.13.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.7.tgz", - "integrity": "sha512-/3BLptG/q0u36eYED7Nhf4fKXmcKb6LjjT7ZMwhZIZSdSxVqDqSTmATW3a56n3KEPQUXCU9TpxAfCBQhs6brVA==", + "version": "5.14.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.11.tgz", + "integrity": "sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==", "dependencies": { - "@babel/runtime": "^7.22.5", + "@babel/runtime": "^7.22.15", "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3541,7 +3603,54 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts": { + "version": "6.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-6.0.0-alpha.13.tgz", + "integrity": "sha512-/FfH55kkhbP3IRbQczvcysp78iTMllqHC4RUDz4wskQVMVCep32slv77aTfgP+XWtMx9lEHPGse238OKwUKLEA==", + "dependencies": { + "@babel/runtime": "^7.22.15", + "@mui/base": "^5.0.0-beta.14", + "clsx": "^2.0.0", + "d3-color": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" } }, "node_modules/@mui/x-data-grid": { @@ -5035,14 +5144,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-modal": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.0.tgz", @@ -8360,6 +8461,100 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -12293,6 +12488,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/package.json b/package.json index a24962578a..1e6591851f 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,11 @@ "@apollo/client": "^3.4.0-beta.19", "@apollo/link-error": "^2.0.0-beta.3", "@apollo/react-testing": "^4.0.0", - "@emotion/react": "^11.11.0", + "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.8.3", - "@mui/material": "^5.13.3", + "@mui/material": "^5.14.1", + "@mui/x-charts": "^6.0.0-alpha.13", "@mui/x-data-grid": "^6.8.0", "@mui/x-date-pickers": "^6.6.0", "@pdfme/generator": "^1.2.6", diff --git a/src/App.test.tsx b/src/App.test.tsx index 710c970628..f8db73a210 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -11,6 +11,14 @@ import { CHECK_AUTH } from 'GraphQl/Queries/Queries'; import i18nForTest from './utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// These modules are used by the Feedback components +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + const MOCKS = [ { request: { diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index d426a4bfb5..02dec2a6f6 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -169,6 +169,20 @@ export const EVENT_CHECKINS = gql` } `; +export const EVENT_FEEDBACKS = gql` + query eventFeedback($id: ID!) { + event(id: $id) { + _id + feedback { + _id + rating + review + } + averageFeedbackScore + } + } +`; + // Query to take the Organization with data export const ORGANIZATIONS_LIST = gql` query Organizations($id: ID!) { diff --git a/src/components/EventStats/EventStats.test.tsx b/src/components/EventStats/EventStats.test.tsx new file mode 100644 index 0000000000..c02e0dd619 --- /dev/null +++ b/src/components/EventStats/EventStats.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { EventStats } from './EventStats'; +import { BrowserRouter } from 'react-router-dom'; +import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; + +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// These modules are used by the Feedback component +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + +const mockData = [ + { + request: { + query: EVENT_FEEDBACKS, + variables: { + id: 'eventStats123', + }, + }, + result: { + data: { + event: { + _id: 'eventStats123', + feedback: [ + { + _id: 'feedback1', + review: 'review1', + rating: 5, + }, + ], + averageFeedbackScore: 5, + }, + }, + }, + }, +]; + +describe('Testing Event Stats', () => { + const props = { + eventId: 'eventStats123', + show: true, + handleClose: jest.fn(), + }; + + test('The stats should be rendered properly', async () => { + const { queryByText } = render( + + + + + + ); + + await waitFor(() => + expect(queryByText('Event Statistics')).toBeInTheDocument() + ); + }); +}); diff --git a/src/components/EventStats/EventStats.tsx b/src/components/EventStats/EventStats.tsx new file mode 100644 index 0000000000..6158c39075 --- /dev/null +++ b/src/components/EventStats/EventStats.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Modal } from 'react-bootstrap'; +import { FeedbackStats } from './Statistics/Feedback'; +import { ReviewStats } from './Statistics/Review'; +import { AverageRating } from './Statistics/AverageRating'; +import Stack from '@mui/material/Stack'; +import styles from './Loader.module.css'; +import { useQuery } from '@apollo/client'; +import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; + +type ModalPropType = { + show: boolean; + eventId: string; + handleClose: () => void; +}; + +export const EventStats = ({ + show, + handleClose, + eventId, +}: ModalPropType): JSX.Element => { + const { data, loading } = useQuery(EVENT_FEEDBACKS, { + variables: { id: eventId }, + }); + + // Render the loading screen + if (loading) { + return ( + <> +
+ + ); + } + + return ( + <> + + + Event Statistics + + + + +
+ + +
+
+
+
+ + ); +}; diff --git a/src/components/EventStats/EventStatsWrapper.test.tsx b/src/components/EventStats/EventStatsWrapper.test.tsx new file mode 100644 index 0000000000..726ffa2b43 --- /dev/null +++ b/src/components/EventStats/EventStatsWrapper.test.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { EventStatsWrapper } from './EventStatsWrapper'; +import { BrowserRouter } from 'react-router-dom'; +import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; + +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + +const mockData = [ + { + request: { + query: EVENT_FEEDBACKS, + variables: { + id: 'eventStats123', + }, + }, + result: { + data: { + event: { + _id: 'eventStats123', + feedback: [ + { + _id: 'feedback1', + review: 'review1', + rating: 5, + }, + ], + averageFeedbackScore: 5, + }, + }, + }, + }, +]; + +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// These modules are used by the Feedback component +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + +describe('Testing Event Stats Wrapper', () => { + const props = { + eventId: 'eventStats123', + }; + + test('The button to open and close the modal should work properly', async () => { + const { queryByText, queryByRole } = render( + + + + + + ); + + // Open the modal + fireEvent.click(queryByText('View Event Statistics') as Element); + + await waitFor(() => + expect(queryByText('Event Statistics')).toBeInTheDocument() + ); + + // Close the modal + fireEvent.click(queryByRole('button', { name: /close/i }) as HTMLElement); + await waitFor(() => + expect(queryByText('Event Statistics')).not.toBeInTheDocument() + ); + }); +}); diff --git a/src/components/EventStats/EventStatsWrapper.tsx b/src/components/EventStats/EventStatsWrapper.tsx new file mode 100644 index 0000000000..953a21a06b --- /dev/null +++ b/src/components/EventStats/EventStatsWrapper.tsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import { EventStats } from './EventStats'; +import { Button } from 'react-bootstrap'; + +type PropType = { + eventId: string; +}; + +export const EventStatsWrapper = (props: PropType): JSX.Element => { + const [showModal, setShowModal] = useState(false); + + return ( + <> + + {showModal && ( + setShowModal(false)} + eventId={props.eventId} + /> + )} + + ); +}; diff --git a/src/components/EventStats/Loader.module.css b/src/components/EventStats/Loader.module.css new file mode 100644 index 0000000000..0f78d81c01 --- /dev/null +++ b/src/components/EventStats/Loader.module.css @@ -0,0 +1,43 @@ +.loader, +.loader:after { + border-radius: 50%; + width: 10em; + height: 10em; +} +.loader { + margin: 60px auto; + margin-top: 35vh !important; + font-size: 10px; + position: relative; + text-indent: -9999em; + border-top: 1.1em solid rgba(255, 255, 255, 0.2); + border-right: 1.1em solid rgba(255, 255, 255, 0.2); + border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); + border-left: 1.1em solid #febc59; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation: load8 1.1s infinite linear; + animation: load8 1.1s infinite linear; +} + +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/src/components/EventStats/Statistics/AverageRating.test.tsx b/src/components/EventStats/Statistics/AverageRating.test.tsx new file mode 100644 index 0000000000..79328965f6 --- /dev/null +++ b/src/components/EventStats/Statistics/AverageRating.test.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { AverageRating } from './AverageRating'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import { I18nextProvider } from 'react-i18next'; +import i18nForTest from 'utils/i18nForTest'; +import { ToastContainer } from 'react-toastify'; + +const props = { + data: { + event: { + _id: '123', + feedback: [ + { + _id: 'feedback1', + review: 'review1', + rating: 5, + }, + { + _id: 'feedback2', + review: 'review2', + rating: 5, + }, + { + _id: 'feedback3', + review: null, + rating: 5, + }, + ], + averageFeedbackScore: 5, + }, + }, +}; + +describe('Testing Average Rating Card', () => { + test('The component should be rendered and the Score should be shown', async () => { + const { queryByText } = render( + + + + + + + + + ); + + await waitFor(() => + expect(queryByText('Average Review Score')).toBeInTheDocument() + ); + + await waitFor(() => + expect(queryByText('Rated 5.00 / 10')).toBeInTheDocument() + ); + }); +}); diff --git a/src/components/EventStats/Statistics/AverageRating.tsx b/src/components/EventStats/Statistics/AverageRating.tsx new file mode 100644 index 0000000000..dac7b208df --- /dev/null +++ b/src/components/EventStats/Statistics/AverageRating.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import Card from 'react-bootstrap/Card'; +import Rating from '@mui/material/Rating'; +import FavoriteIcon from '@mui/icons-material/Favorite'; +import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; +import Typography from '@mui/material/Typography'; +import { styled } from '@mui/material/styles'; + +type ModalPropType = { + data: { + event: { + _id: string; + averageFeedbackScore: number | null; + feedback: FeedbackType[]; + }; + }; +}; + +type FeedbackType = { + _id: string; + rating: number; + review: string | null; +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const StyledRating = styled(Rating)({ + '& .MuiRating-iconFilled': { + color: '#ff6d75', + }, + '& .MuiRating-iconHover': { + color: '#ff3d47', + }, +}); + +export const AverageRating = ({ data }: ModalPropType): JSX.Element => { + return ( + <> + + + +

Average Review Score

+
+ + Rated {(data.event.averageFeedbackScore || 0).toFixed(2)} / 10 + + } + size="medium" + emptyIcon={} + /> +
+
+ + ); +}; diff --git a/src/components/EventStats/Statistics/Feedback.test.tsx b/src/components/EventStats/Statistics/Feedback.test.tsx new file mode 100644 index 0000000000..0a8b5a571d --- /dev/null +++ b/src/components/EventStats/Statistics/Feedback.test.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { FeedbackStats } from './Feedback'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import { I18nextProvider } from 'react-i18next'; +import i18nForTest from 'utils/i18nForTest'; +import { ToastContainer } from 'react-toastify'; + +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + +const nonEmptyProps = { + data: { + event: { + _id: '123', + feedback: [ + { + _id: 'feedback1', + review: 'review1', + rating: 5, + }, + { + _id: 'feedback2', + review: 'review2', + rating: 5, + }, + { + _id: 'feedback3', + review: null, + rating: 5, + }, + ], + averageFeedbackScore: 5, + }, + }, +}; + +const emptyProps = { + data: { + event: { + _id: '123', + feedback: [], + averageFeedbackScore: 5, + }, + }, +}; + +describe('Testing Feedback Statistics Card', () => { + test('The component should be rendered and the feedback should be shown if present', async () => { + const { queryByText } = render( + + + + + + + + + ); + + await waitFor(() => + expect(queryByText('Feedback Analysis')).toBeInTheDocument() + ); + + await waitFor(() => + expect( + queryByText('3 people have filled feedback for this event.') + ).toBeInTheDocument() + ); + }); + + test('The component should be rendered and message should be shown if no feedback is present', async () => { + const { queryByText } = render( + + + + + + + + + ); + + await waitFor(() => + expect(queryByText('Feedback Analysis')).toBeInTheDocument() + ); + + await waitFor(() => + expect( + queryByText('Please ask attendees to submit feedback for insights!') + ).toBeInTheDocument() + ); + }); +}); diff --git a/src/components/EventStats/Statistics/Feedback.tsx b/src/components/EventStats/Statistics/Feedback.tsx new file mode 100644 index 0000000000..6a78e29884 --- /dev/null +++ b/src/components/EventStats/Statistics/Feedback.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { + PieChart, + pieArcClasses, + pieArcLabelClasses, +} from '@mui/x-charts/PieChart'; +import Card from 'react-bootstrap/Card'; + +type ModalPropType = { + data: { + event: { + _id: string; + averageFeedbackScore: number | null; + feedback: FeedbackType[]; + }; + }; +}; + +type FeedbackType = { + _id: string; + rating: number; + review: string | null; +}; + +export const FeedbackStats = ({ data }: ModalPropType): JSX.Element => { + const ratingColors = [ + '#57bb8a', // Green + '#73b87e', + '#94bd77', + '#b0be6e', + '#d4c86a', + '#f5ce62', + '#e9b861', + '#ecac67', + '#e79a69', + '#e2886c', + '#dd776e', // Red + ]; + + const count: Record = {}; + + data.event.feedback.forEach((feedback: FeedbackType) => { + if (feedback.rating in count) count[feedback.rating]++; + else count[feedback.rating] = 1; + }); + + const chartData = []; + for (let rating = 0; rating <= 10; rating++) { + if (rating in count) + chartData.push({ + id: rating, + value: count[rating], + label: `${rating} (${count[rating]})`, + color: ratingColors[10 - rating], + }); + } + + return ( + <> + + + +

Feedback Analysis

+
+
+ {data.event.feedback.length} people have filled feedback for this + event. +
+ {data.event.feedback.length ? ( + `${item.id} (${item.value})`, + innerRadius: 30, + outerRadius: 120, + paddingAngle: 2, + cornerRadius: 5, + startAngle: 0, + highlightScope: { faded: 'global', highlighted: 'item' }, + faded: { innerRadius: 30, additionalRadius: -30 }, + }, + ]} + sx={{ + [`& .${pieArcClasses.faded}`]: { + fill: 'gray', + }, + [`& .${pieArcLabelClasses.root}`]: { + fill: 'black', + fontSize: '15px', + }, + [`& .${pieArcLabelClasses.faded}`]: { + display: 'none', + }, + }} + width={380} + height={380} + /> + ) : ( + <>Please ask attendees to submit feedback for insights! + )} +
+
+ + ); +}; diff --git a/src/components/EventStats/Statistics/Review.test.tsx b/src/components/EventStats/Statistics/Review.test.tsx new file mode 100644 index 0000000000..ee9f79c54c --- /dev/null +++ b/src/components/EventStats/Statistics/Review.test.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { ReviewStats } from './Review'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import { I18nextProvider } from 'react-i18next'; +import i18nForTest from 'utils/i18nForTest'; +import { ToastContainer } from 'react-toastify'; + +const nonEmptyReviewProps = { + data: { + event: { + _id: '123', + feedback: [ + { + _id: 'feedback1', + review: 'review1', + rating: 5, + }, + { + _id: 'feedback2', + review: 'review2', + rating: 5, + }, + { + _id: 'feedback3', + review: null, + rating: 5, + }, + ], + averageFeedbackScore: 5, + }, + }, +}; + +const emptyReviewProps = { + data: { + event: { + _id: '123', + feedback: [ + { + _id: 'feedback3', + review: null, + rating: 5, + }, + ], + averageFeedbackScore: 5, + }, + }, +}; + +describe('Testing Review Statistics Card', () => { + test('The component should be rendered and the reviews should be shown if present', async () => { + const { queryByText } = render( + + + + + + + + + ); + + await waitFor(() => expect(queryByText('Reviews')).toBeInTheDocument()); + + await waitFor(() => + expect(queryByText('Filled by 2 people.')).toBeInTheDocument() + ); + + await waitFor(() => expect(queryByText('review2')).toBeInTheDocument()); + }); + + test('The component should be rendered and message should be shown if no review is present', async () => { + const { queryByText } = render( + + + + + + + + + ); + + await waitFor(() => expect(queryByText('Reviews')).toBeInTheDocument()); + + await waitFor(() => + expect( + queryByText('Waiting for people to talk about the event...') + ).toBeInTheDocument() + ); + }); +}); diff --git a/src/components/EventStats/Statistics/Review.tsx b/src/components/EventStats/Statistics/Review.tsx new file mode 100644 index 0000000000..0528de79d3 --- /dev/null +++ b/src/components/EventStats/Statistics/Review.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import Card from 'react-bootstrap/Card'; +import Rating from '@mui/material/Rating'; + +type ModalPropType = { + data: { + event: { + _id: string; + averageFeedbackScore: number | null; + feedback: FeedbackType[]; + }; + }; +}; + +type FeedbackType = { + _id: string; + rating: number; + review: string | null; +}; + +export const ReviewStats = ({ data }: ModalPropType): JSX.Element => { + const reviews = data.event.feedback.filter( + (feedback: FeedbackType) => feedback.review != null + ); + + return ( + <> + + + +

Reviews

+
+
Filled by {reviews.length} people.
+ {reviews.length ? ( + reviews.map((review) => ( +
+
+ +

{review.review}

+
+
+ )) + ) : ( + <>Waiting for people to talk about the event... + )} +
+
+ + ); +}; diff --git a/src/screens/EventDashboard/EventDashboard.test.tsx b/src/screens/EventDashboard/EventDashboard.test.tsx index a5452429e5..2a3340be10 100644 --- a/src/screens/EventDashboard/EventDashboard.test.tsx +++ b/src/screens/EventDashboard/EventDashboard.test.tsx @@ -17,6 +17,14 @@ import { queryMockWithProjectAndTask, } from './EventDashboard.mocks'; +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// These modules are used by the Feedback components +jest.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: jest.fn(), + PieChart: jest.fn().mockImplementation(() => <>Test), + pieArcClasses: jest.fn(), +})); + describe('Testing Event Dashboard Screen', () => { beforeEach(() => { global.window = Object.create(window); diff --git a/src/screens/EventDashboard/EventDashboard.tsx b/src/screens/EventDashboard/EventDashboard.tsx index 0c48f30f12..198e31e508 100644 --- a/src/screens/EventDashboard/EventDashboard.tsx +++ b/src/screens/EventDashboard/EventDashboard.tsx @@ -8,6 +8,7 @@ import { AddEventProjectModal } from 'components/EventProjectModals/AddEventProj import { UpdateEventProjectModal } from 'components/EventProjectModals/UpdateEventProjectModal'; import { DeleteEventProjectModal } from 'components/EventProjectModals/DeleteEventProjectModal'; import { AddTaskModal } from 'components/TaskModals/AddTaskModal'; +import { EventStatsWrapper } from 'components/EventStats/EventStatsWrapper'; import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; import Button from 'react-bootstrap/Button'; import List from '@mui/material/List'; @@ -96,7 +97,7 @@ const EventDashboard = (): JSX.Element => { : ``}

- Registrantss: {eventData.event.attendees.length} + Registrants: {eventData.event.attendees.length}


{/* Buttons to trigger different modals */} @@ -117,6 +118,7 @@ const EventDashboard = (): JSX.Element => { orgId={eventData.event.organization._id} /> +