diff --git a/package-lock.json b/package-lock.json index ba093032bf..4b09f393c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@mui/material": "^5.15.19", "@mui/private-theming": "^5.15.12", "@mui/system": "^5.14.12", - "@mui/x-charts": "^7.4.0", + "@mui/x-charts": "^7.6.2", "@mui/x-data-grid": "^6.20.0", "@mui/x-date-pickers": "^7.6.1", "@pdfme/generator": "^1.2.6", @@ -26,12 +26,12 @@ "customize-cra": "^1.0.0", "dayjs": "^1.11.11", "flag-icons": "^6.6.6", - "graphql": "^16.8.1", + "graphql": "^16.8.2", "graphql-tag": "^2.12.6", "graphql-ws": "^5.16.0", "history": "^5.3.0", "i18next": "^21.8.14", - "i18next-browser-languagedetector": "^6.1.4", + "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^1.4.1", "inquirer": "^8.0.0", "js-cookie": "^3.0.1", @@ -48,13 +48,13 @@ "react-infinite-scroll-component": "^6.1.0", "react-multi-carousel": "^2.8.5", "react-redux": "^7.2.5", - "react-router-dom": "^6.22.2", + "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "react-toastify": "^9.0.3", "redux": "^4.1.1", "redux-thunk": "^2.3.0", "sanitize-html": "^2.13.0", - "typedoc-plugin-markdown": "^4.0.2", + "typedoc-plugin-markdown": "^4.0.3", "typescript": "^4.3.5", "web-vitals": "^1.0.1" }, @@ -90,10 +90,10 @@ "jest-localstorage-mock": "^2.4.19", "jest-location-mock": "^2.0.0", "jest-preview": "^0.3.1", - "lint-staged": "^15.2.2", + "lint-staged": "^15.2.5", "postcss-modules": "^6.0.0", "sass": "^1.77.4", - "tsx": "^3.11.0" + "tsx": "^4.15.5" }, "engines": { "node": ">=20.x" @@ -3155,10 +3155,266 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -3171,6 +3427,102 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4124,13 +4476,13 @@ } }, "node_modules/@mui/x-charts": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.4.0.tgz", - "integrity": "sha512-W6A0ZJmfXLeAtuml0Yi7gvjxS6aW/2h6uO9PQNuE/rpV0iIEMU5bVfcJZGMVlh0WY+43FEicI1/8n6FzMcfZdg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.6.2.tgz", + "integrity": "sha512-oG22NRno1+HSLV/9jVLThnHAKN4g+MXOO6GqaQxN9LM0hjt1tgRsrNAlfcJndmj/dVwqFtynkFB5qWnTEBZs7Q==", "dependencies": { - "@babel/runtime": "^7.24.0", + "@babel/runtime": "^7.24.6", "@mui/base": "^5.0.0-beta.40", - "@mui/system": "^5.15.14", + "@mui/system": "^5.15.15", "@mui/utils": "^5.15.14", "@react-spring/rafz": "^9.7.3", "@react-spring/web": "^9.7.3", @@ -4614,9 +4966,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz", - "integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", "engines": { "node": ">=14.0.0" } @@ -7442,11 +7794,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -9905,9 +10257,9 @@ } }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -9917,28 +10269,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -11221,9 +11574,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11802,9 +12155,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -12063,9 +12416,9 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/graphql": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", - "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.2.tgz", + "integrity": "sha512-cvVIBILwuoSyD54U4cF/UXDh5yAobhNV/tPygI4lZhgOIJQE/WLWC4waBRb4I6bDVYb3OVx3lfHbaQOEoUD5sg==", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -12739,11 +13092,11 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.8.tgz", - "integrity": "sha512-Svm+MduCElO0Meqpj1kJAriTC6OhI41VhlT/A0UPjGoPZBhAHIaGE5EfsHlTpgdH09UVX7rcc72pSDDBeKSQQA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", "dependencies": { - "@babel/runtime": "^7.19.0" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-http-backend": { @@ -15737,21 +16090,21 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/lint-staged": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", - "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", - "dev": true, - "dependencies": { - "chalk": "5.3.0", - "commander": "11.1.0", - "debug": "4.3.4", - "execa": "8.0.1", - "lilconfig": "3.0.0", - "listr2": "8.0.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.4" + "version": "15.2.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz", + "integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.4", + "execa": "~8.0.1", + "lilconfig": "~3.1.1", + "listr2": "~8.2.1", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.4.2" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -15776,12 +16129,12 @@ } }, "node_modules/lint-staged/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/lint-staged/node_modules/execa": { @@ -15841,12 +16194,15 @@ } }, "node_modules/lint-staged/node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lint-staged/node_modules/mimic-fn": { @@ -15928,10 +16284,13 @@ } }, "node_modules/lint-staged/node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", "dev": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -15992,16 +16351,16 @@ } }, "node_modules/listr2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", - "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz", + "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==", "dev": true, "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.0.0", - "rfdc": "^1.3.0", + "rfdc": "^1.3.1", "wrap-ansi": "^9.0.0" }, "engines": { @@ -16233,13 +16592,10 @@ } }, "node_modules/log-update/node_modules/ansi-escapes": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", - "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", "dev": true, - "dependencies": { - "type-fest": "^3.0.0" - }, "engines": { "node": ">=14.16" }, @@ -16340,18 +16696,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -16618,11 +16962,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -20146,11 +20490,11 @@ } }, "node_modules/react-router": { - "version": "6.22.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz", - "integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", "dependencies": { - "@remix-run/router": "1.15.2" + "@remix-run/router": "1.16.1" }, "engines": { "node": ">=14.0.0" @@ -20160,12 +20504,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz", - "integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", + "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", "dependencies": { - "@remix-run/router": "1.15.2", - "react-router": "6.22.2" + "@remix-run/router": "1.16.1", + "react-router": "6.23.1" }, "engines": { "node": ">=14.0.0" @@ -23214,18 +23558,20 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tsx": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.14.0.tgz", - "integrity": "sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==", + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.15.5.tgz", + "integrity": "sha512-iKi8jQ2VBmZ2kU/FkGkL2OSHBHsazsUzsdC/W/RwhKIEsIoZ1alCclZHP5jGfNHEaEWUJFM1GquzCf+4db3b0w==", "dev": true, "dependencies": { - "esbuild": "~0.18.20", - "get-tsconfig": "^4.7.2", - "source-map-support": "^0.5.21" + "esbuild": "~0.21.4", + "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" }, + "engines": { + "node": ">=18.0.0" + }, "optionalDependencies": { "fsevents": "~2.3.3" } @@ -23396,9 +23742,9 @@ } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.0.2.tgz", - "integrity": "sha512-4MV3M+0lsmIaXuDBzeqLYemZqwTQDWQow+o8zdT9hC7KFu06GaFo2uUEbkjE6pgZA9hnkOTtzRVd0R9YJWcH8A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.0.3.tgz", + "integrity": "sha512-0tZbeVGGCd4+lpoIX+yHWgUfyaLZCQCgJOpuVdTtOtD3+jKaedJ4sl/tkNaYBPeWVKiyDkSHfGuHkq53jlzIFg==", "peerDependencies": { "typedoc": "0.25.x" } diff --git a/package.json b/package.json index fe465652fc..976ddd8709 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@mui/material": "^5.15.19", "@mui/private-theming": "^5.15.12", "@mui/system": "^5.14.12", - "@mui/x-charts": "^7.4.0", + "@mui/x-charts": "^7.6.2", "@mui/x-data-grid": "^6.20.0", "@mui/x-date-pickers": "^7.6.1", "@pdfme/generator": "^1.2.6", @@ -24,12 +24,12 @@ "customize-cra": "^1.0.0", "dayjs": "^1.11.11", "flag-icons": "^6.6.6", - "graphql": "^16.8.1", + "graphql": "^16.8.2", "graphql-tag": "^2.12.6", "graphql-ws": "^5.16.0", "history": "^5.3.0", "i18next": "^21.8.14", - "i18next-browser-languagedetector": "^6.1.4", + "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^1.4.1", "inquirer": "^8.0.0", "js-cookie": "^3.0.1", @@ -46,13 +46,13 @@ "react-infinite-scroll-component": "^6.1.0", "react-multi-carousel": "^2.8.5", "react-redux": "^7.2.5", - "react-router-dom": "^6.22.2", + "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "react-toastify": "^9.0.3", "redux": "^4.1.1", "redux-thunk": "^2.3.0", "sanitize-html": "^2.13.0", - "typedoc-plugin-markdown": "^4.0.2", + "typedoc-plugin-markdown": "^4.0.3", "typescript": "^4.3.5", "web-vitals": "^1.0.1" }, @@ -123,10 +123,10 @@ "jest-localstorage-mock": "^2.4.19", "jest-location-mock": "^2.0.0", "jest-preview": "^0.3.1", - "lint-staged": "^15.2.2", + "lint-staged": "^15.2.5", "postcss-modules": "^6.0.0", "sass": "^1.77.4", - "tsx": "^3.11.0" + "tsx": "^4.15.5" }, "resolutions": { "@apollo/client": "^3.4.0-beta.19", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 6b2e9b40bd..8ee4040568 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -279,6 +279,23 @@ "successfulDeletion": "Action Item deleted successfully", "title": "Action Items" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "Agenda Category Details", + "updateAgendaCategory": "Update Agenda Category", + "title": "Agenda Categories", + "name": "Category", + "description": "Description", + "createdBy": "Created By", + "options": "Options", + "createAgendaCategory": "Create Agenda Category", + "noAgendaCategories": "No Agenda Categories", + "update": "Update", + "agendaCategoryCreated": "Agenda Category created successfully", + "agendaCategoryUpdated": "Agenda Category updated successfully", + "agendaCategoryDeleted": "Agenda Category deleted successfully", + "deleteAgendaCategory": "Delete Agenda Category", + "deleteAgendaCategoryMsg": "Do you want to remove this agenda category?" + }, "eventListCard": { "deleteEvent": "Delete Event", "deleteEventMsg": "Do you want to remove this event?", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 88ca03c90d..cb0c7798d7 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -282,6 +282,23 @@ "successfulDeletion": "Élément d'action supprimé avec succès", "title": "Éléments d'action" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "Détails de la catégorie d'ordre du jour", + "updateAgendaCategory": "Mettre à jour la catégorie d'ordre du jour", + "title": "Catégories d'ordre du jour", + "name": "Catégorie", + "description": "Description", + "createdBy": "Créé par", + "options": "Options", + "createAgendaCategory": "Créer une catégorie d'ordre du jour", + "noAgendaCategories": "Aucune catégorie d'ordre du jour", + "update": "Mettre à jour", + "agendaCategoryCreated": "Catégorie d'ordre du jour créée avec succès", + "agendaCategoryUpdated": "Catégorie d'ordre du jour mise à jour avec succès", + "agendaCategoryDeleted": "Catégorie d'ordre du jour supprimée avec succès", + "deleteAgendaCategory": "Supprimer la catégorie d'ordre du jour", + "deleteAgendaCategoryMsg": "Souhaitez-vous supprimer cette catégorie d'ordre du jour ?" + }, "eventListCard": { "deleteEvent": "Supprimer l'événement", "deleteEventMsg": "Voulez-vous supprimer cet événement ?", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 5a3adc5fa4..69144d09ef 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -282,6 +282,23 @@ "successfulDeletion": "कार्रवाई आइटम सफलतापूर्वक हटा दिया गया", "title": "एक्शन आइटम्स" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "एजेंडा श्रेणी विवरण", + "updateAgendaCategory": "एजेंडा श्रेणी अपडेट करें", + "title": "एजेंडा श्रेणियाँ", + "name": "श्रेणी", + "description": "विवरण", + "createdBy": "द्वारा बनाया गया", + "options": "विकल्प", + "createAgendaCategory": "एजेंडा श्रेणी बनाएं", + "noAgendaCategories": "कोई एजेंडा श्रेणी नहीं", + "update": "अपडेट करें", + "agendaCategoryCreated": "एजेंडा श्रेणी सफलतापूर्वक बनाई गई", + "agendaCategoryUpdated": "एजेंडा श्रेणी सफलतापूर्वक अपडेट की गई", + "agendaCategoryDeleted": "एजेंडा श्रेणी सफलतापूर्वक हटा दी गई", + "deleteAgendaCategory": "एजेंडा श्रेणी हटाएं", + "deleteAgendaCategoryMsg": "क्या आप इस एजेंडा श्रेणी को हटाना चाहते हैं?" + }, "eventListCard": { "deleteEvent": "ईवेंट हटाएँ", "deleteEventMsg": "क्या आप इस ईवेंट को हटाना चाहते हैं?", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 27c21476be..c4ba57ab81 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -398,6 +398,23 @@ "title": "Ítems de acción", "yes": "Sí" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "Detalles de la categoría de la agenda", + "updateAgendaCategory": "Actualizar categoría de la agenda", + "title": "Categorías de la agenda", + "name": "Categoría", + "description": "Descripción", + "createdBy": "Creado por", + "options": "Opciones", + "createAgendaCategory": "Crear categoría de la agenda", + "noAgendaCategories": "No hay categorías de la agenda", + "update": "Actualizar", + "agendaCategoryCreated": "Categoría de la agenda creada exitosamente", + "agendaCategoryUpdated": "Categoría de la agenda actualizada exitosamente", + "agendaCategoryDeleted": "Categoría de la agenda eliminada exitosamente", + "deleteAgendaCategory": "Eliminar categoría de la agenda", + "deleteAgendaCategoryMsg": "¿Desea eliminar esta categoría de la agenda?" + }, "eventListCard": { "location": "Lugar del evento", "deleteEvent": "Eliminar evento", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 06bb5cb398..c29824067c 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -282,6 +282,23 @@ "successfulDeletion": "操作项已成功删除", "title": "行动项目" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "议程类别详情", + "updateAgendaCategory": "更新议程类别", + "title": "议程类别", + "name": "类别", + "description": "描述", + "createdBy": "创建人", + "options": "选项", + "createAgendaCategory": "创建议程类别", + "noAgendaCategories": "没有议程类别", + "update": "更新", + "agendaCategoryCreated": "议程类别创建成功", + "agendaCategoryUpdated": "议程类别更新成功", + "agendaCategoryDeleted": "议程类别删除成功", + "deleteAgendaCategory": "删除议程类别", + "deleteAgendaCategoryMsg": "是否要删除此议程类别?" + }, "eventListCard": { "deleteEvent": "删除事件", "deleteEventMsg": "您想删除此事件吗?", diff --git a/schema.graphql b/schema.graphql index 35c49f2cd4..b123bdb7c6 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1440,6 +1440,8 @@ enum UserOrderByInput { id_DESC lastName_ASC lastName_DESC + createdAt_ASC + createdAt_DESC } type UserPhone { diff --git a/src/App.tsx b/src/App.tsx index 1d8787e7eb..d2e5d8306e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import OrgList from 'screens/OrgList/OrgList'; import OrgPost from 'screens/OrgPost/OrgPost'; import OrgSettings from 'screens/OrgSettings/OrgSettings'; import OrganizationActionItems from 'screens/OrganizationActionItems/OrganizationActionItems'; +import OrganizationAgendaCategory from 'screens/OrganizationAgendaCategory/OrganizationAgendaCategory'; import OrganizationDashboard from 'screens/OrganizationDashboard/OrganizationDashboard'; import OrganizationEvents from 'screens/OrganizationEvents/OrganizationEvents'; import OrganizaitionFundCampiagn from 'screens/OrganizationFundCampaign/OrganizationFundCampagins'; @@ -135,6 +136,10 @@ function app(): JSX.Element { path="/orgactionitems/:orgId" element={} /> + } + /> } /> checklist \ No newline at end of file diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.module.css b/src/components/AgendaCategory/AgendaCategoryContainer.module.css new file mode 100644 index 0000000000..7ad16b4c7c --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainer.module.css @@ -0,0 +1,20 @@ +.createModal { + margin-top: 20vh; + margin-left: 13vw; + max-width: 80vw; +} + +.titlemodal { + color: var(--bs-gray-600); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--bs-primary); + width: 65%; +} + +.agendaCategoryOptionsButton { + width: 24px; + height: 24px; +} diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx new file mode 100644 index 0000000000..5ddab82aec --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx @@ -0,0 +1,415 @@ +import React from 'react'; +import { + render, + screen, + waitFor, + act, + waitForElementToBeRemoved, + fireEvent, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import 'jest-localstorage-mock'; +import { MockedProvider } from '@apollo/client/testing'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import i18nForTest from 'utils/i18nForTest'; +import { toast } from 'react-toastify'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import AgendaCategoryContainer from './AgendaCategoryContainer'; +import { props, props2 } from './AgendaCategoryContainerProps'; +import { MOCKS, MOCKS_ERROR_MUTATIONS } from './AgendaCategoryContainerMocks'; + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +async function wait(ms = 100): Promise { + await act(async () => { + return new Promise((resolve) => setTimeout(resolve, ms)); + }); +} + +const translations = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.organizationAgendaCategory, + ), +); + +describe('Testing Agenda Category Component', () => { + const formData = { + name: 'AgendaCategory 1 Edited', + description: 'AgendaCategory 1 Description Edited', + }; + + test('component loads correctly with categories', async () => { + render( + + + + + + + + + , + ); + await wait(); + + await waitFor(() => { + expect( + screen.queryByText(translations.noAgendaCategories), + ).not.toBeInTheDocument(); + }); + }); + + test('component loads correctly with no agenda Categories', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.queryByText(translations.noAgendaCategories), + ).toBeInTheDocument(); + }); + }); + + test('opens and closes the update modal correctly', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editAgendCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editAgendCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('updateAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('updateAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('updateAgendaCategoryModalCloseBtn'), + ); + }); + + test('opens and closes the preview modal correctly', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('previewAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('previewAgendaCategoryModalCloseBtn'), + ); + }); + + test('opens and closes the update and delete modals through the preview modal', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteAgendaCategoryModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('deleteAgendaCategoryCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('deleteAgendaCategoryCloseBtn'), + ); + + await waitFor(() => { + expect( + screen.getByTestId('editAgendaCategoryPreviewModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editAgendaCategoryPreviewModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('updateAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('updateAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('updateAgendaCategoryModalCloseBtn'), + ); + }); + + test('updates an agenda category and toasts success', async () => { + render( + + + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editAgendCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editAgendCategoryModalBtn')[0]); + + const name = screen.getByPlaceholderText(translations.name); + const description = screen.getByPlaceholderText(translations.description); + + fireEvent.change(name, { target: { value: '' } }); + userEvent.type(name, formData.name); + + fireEvent.change(description, { target: { value: '' } }); + userEvent.type(description, formData.description); + + await waitFor(() => { + expect(screen.getByTestId('editAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.agendaCategoryUpdated); + }); + }); + + test('toasts error on unsuccessful updation', async () => { + render( + + + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editAgendCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editAgendCategoryModalBtn')[0]); + + const nameInput = screen.getByLabelText(translations.name); + const descriptionInput = screen.getByLabelText(translations.description); + fireEvent.change(nameInput, { target: { value: '' } }); + fireEvent.change(descriptionInput, { + target: { value: '' }, + }); + userEvent.type(nameInput, formData.name); + userEvent.type(descriptionInput, formData.description); + + await waitFor(() => { + expect(screen.getByTestId('editAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); + + test('deletes the agenda category and toasts success', async () => { + render( + + + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteAgendaCategoryModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('deleteAgendaCategoryCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + userEvent.click(screen.getByTestId('deleteAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.agendaCategoryDeleted); + }); + }); + + test('toasts error on unsuccessful deletion', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteAgendaCategoryModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('deleteAgendaCategoryCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.tsx new file mode 100644 index 0000000000..e803e77172 --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainer.tsx @@ -0,0 +1,272 @@ +import React, { useState } from 'react'; +import type { ChangeEvent } from 'react'; +import { Button, Col, Row } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { useMutation } from '@apollo/client'; + +import { + DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/mutations'; +import type { InterfaceAgendaItemCategoryInfo } from 'utils/interfaces'; +import styles from './AgendaCategoryContainer.module.css'; + +import AgendaCategoryDeleteModal from 'screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal'; +import AgendaCategoryPreviewModal from 'screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal'; +import AgendaCategoryUpdateModal from 'screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal'; + +function agendaCategoryContainer({ + agendaCategoryConnection, + agendaCategoryData, + agendaCategoryRefetch, +}: { + agendaCategoryConnection: 'Organization'; + agendaCategoryData: InterfaceAgendaItemCategoryInfo[] | undefined; + agendaCategoryRefetch: () => void; +}): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationAgendaCategory', + }); + const { t: tCommon } = useTranslation('common'); + const [ + agendaCategoryPreviewModalIsOpen, + setAgendaCategoryPreviewModalIsOpen, + ] = useState(false); + const [agendaCategoryUpdateModalIsOpen, setAgendaCategoryUpdateModalIsOpen] = + useState(false); + const [agendaCategoryDeleteModalIsOpen, setAgendaCategoryDeleteModalIsOpen] = + useState(false); + + const [agendaCategoryId, setAgendaCategoryId] = useState(''); + + const [formState, setFormState] = useState({ + name: '', + description: '', + createdBy: '', + }); + + const showPreviewModal = ( + agendaItemCategory: InterfaceAgendaItemCategoryInfo, + ): void => { + setAgendaCategoryState(agendaItemCategory); + setAgendaCategoryPreviewModalIsOpen(true); + }; + + const hidePreviewModal = (): void => { + setAgendaCategoryPreviewModalIsOpen(false); + }; + + const showUpdateModal = (): void => { + setAgendaCategoryUpdateModalIsOpen(!agendaCategoryUpdateModalIsOpen); + }; + + const hideUpdateModal = (): void => { + setAgendaCategoryUpdateModalIsOpen(!agendaCategoryUpdateModalIsOpen); + }; + + const toggleDeleteModal = (): void => { + setAgendaCategoryDeleteModalIsOpen(!agendaCategoryDeleteModalIsOpen); + }; + + const [updateAgendaCategory] = useMutation( + UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + ); + + const updateAgendaCategoryHandler = async ( + event: ChangeEvent, + ): Promise => { + event.preventDefault(); + try { + await updateAgendaCategory({ + variables: { + updateAgendaCategoryId: agendaCategoryId, + input: { + name: formState.name, + description: formState.description, + }, + }, + }); + + agendaCategoryRefetch(); + hideUpdateModal(); + toast.success(t('agendaCategoryUpdated')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(`Agenda Category Update Failed ${error.message}`); + } + } + }; + + const [deleteAgendaCategory] = useMutation( + DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + ); + + const deleteAgendaCategoryHandler = async (): Promise => { + try { + await deleteAgendaCategory({ + variables: { + deleteAgendaCategoryId: agendaCategoryId, + }, + }); + agendaCategoryRefetch(); + toggleDeleteModal(); + toast.success(t('agendaCategoryDeleted')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(`Agenda Category Delete Failed, ${error.message}`); + } + } + }; + + const handleEditClick = ( + agendaItemCategory: InterfaceAgendaItemCategoryInfo, + ): void => { + setAgendaCategoryState(agendaItemCategory); + showUpdateModal(); + }; + + const setAgendaCategoryState = ( + agendaItemCategory: InterfaceAgendaItemCategoryInfo, + ): void => { + setFormState({ + ...formState, + name: `${agendaItemCategory.name} `, + description: `${agendaItemCategory.description}`, + createdBy: `${agendaItemCategory.createdBy.firstName} ${agendaItemCategory.createdBy.lastName}`, + }); + setAgendaCategoryId(agendaItemCategory._id); + }; + + return ( + <> +
+
+ + +
{t('name')}
+ + + {t('description')} + + +
{t('createdBy')}
+ + +
{t('options')}
+ +
+
+
+ {agendaCategoryData?.map((agendaCategory, index) => ( +
+ + + {`${agendaCategory.name}`} + + + {agendaCategory.description} + + + {`${agendaCategory.createdBy.firstName} ${agendaCategory.createdBy.lastName}`} + + + +
+ + +
+ +
+ + {index !== agendaCategoryData.length - 1 && ( +
+ )} +
+ ))} + {agendaCategoryData?.length === 0 && ( +
+ {t('noAgendaCategories')} +
+ )} +
+
+ + {/* Preview model */} + + {/* Update model */} + + {/* Delete model */} + + + ); +} + +export default agendaCategoryContainer; diff --git a/src/components/AgendaCategory/AgendaCategoryContainerMocks.ts b/src/components/AgendaCategory/AgendaCategoryContainerMocks.ts new file mode 100644 index 0000000000..f5dee5cffd --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainerMocks.ts @@ -0,0 +1,104 @@ +import { + UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + DELETE_AGENDA_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/AgendaCategoryMutations'; + +export const MOCKS = [ + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory1', + input: { + name: 'AgendaCategory 1 Edited', + description: 'AgendaCategory 1 Description Edited', + }, + }, + }, + result: { + data: { + updateAgendaCategory: { + _id: 'agendaCategory1', + }, + }, + }, + }, + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory1', + input: { + name: 'AgendaCategory 1', + description: 'AgendaCategory 1 Description', + }, + }, + }, + result: { + data: { + updateAgendaCategory: { + _id: 'agendaCategory1', + }, + }, + }, + }, + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory2', + input: { + name: 'AgendaCategory 2 edited', + description: 'AgendaCategory 2 Description', + }, + }, + }, + result: { + data: { + updateAgendaCategory: { + _id: 'agendaCategory2', + }, + }, + }, + }, + { + request: { + query: DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + deleteAgendaCategoryId: 'agendaCategory1', + }, + }, + result: { + data: { + deleteAgendaCategory: { + _id: 'agendaCategory1', + }, + }, + }, + }, +]; + +export const MOCKS_ERROR_MUTATIONS = [ + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory1', + input: { + name: 'AgendaCategory 1 Edited', + description: 'AgendaCategory 1 Description Edited', + }, + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + deleteAgendaCategoryId: 'agendaCategory1', + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/components/AgendaCategory/AgendaCategoryContainerProps.ts b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts new file mode 100644 index 0000000000..5181eec153 --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts @@ -0,0 +1,34 @@ +type AgendaCategoryConnectionType = 'Organization'; + +export const props = { + agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, + agendaCategoryData: [ + { + _id: 'agendaCategory1', + name: 'AgendaCategory 1', + description: 'AgendaCategory 1 Description', + createdBy: { + _id: 'user0', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + { + _id: 'agendaCategory2', + name: 'AgendaCategory 2', + description: 'AgendaCategory 2 Description', + createdBy: { + _id: 'user0', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + ], + agendaCategoryRefetch: jest.fn(), +}; + +export const props2 = { + agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, + agendaCategoryData: [], + agendaCategoryRefetch: jest.fn(), +}; diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index d65982edef..17f9af8b32 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -1,5 +1,6 @@ import { QuestionMarkOutlined } from '@mui/icons-material'; import { ReactComponent as ActionItemIcon } from 'assets/svgs/actionItem.svg'; +import { ReactComponent as AgendaCategoryIcon } from 'assets/svgs/agenda-category-icon.svg'; import { ReactComponent as BlockUserIcon } from 'assets/svgs/blockUser.svg'; import { ReactComponent as CheckInRegistrantsIcon } from 'assets/svgs/checkInRegistrants.svg'; import { ReactComponent as DashboardIcon } from 'assets/svgs/dashboard.svg'; @@ -14,8 +15,8 @@ import { ReactComponent as PostsIcon } from 'assets/svgs/posts.svg'; import { ReactComponent as SettingsIcon } from 'assets/svgs/settings.svg'; import { ReactComponent as VenueIcon } from 'assets/svgs/venues.svg'; import { ReactComponent as RequestsIcon } from 'assets/svgs/requests.svg'; -import { ReactComponent as HomeIcon } from 'assets/svgs/home.svg'; -import { ReactComponent as DonateIcon } from 'assets/svgs/donate.svg'; +// import { ReactComponent as HomeIcon } from 'assets/svgs/home.svg'; +// import { ReactComponent as DonateIcon } from 'assets/svgs/donate.svg'; import React from 'react'; @@ -54,6 +55,13 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { data-testid="Icon-Component-ActionItemIcon" /> ); + case 'Agenda Items Category': + return ( + + ); case 'Posts': return ; case 'Block/Unblock': diff --git a/src/components/OrganizationScreen/OrganizationScreen.tsx b/src/components/OrganizationScreen/OrganizationScreen.tsx index 59b6f289c9..a8f9d16b9c 100644 --- a/src/components/OrganizationScreen/OrganizationScreen.tsx +++ b/src/components/OrganizationScreen/OrganizationScreen.tsx @@ -112,6 +112,7 @@ const map: InterfaceMapType = { member: 'memberDetail', orgevents: 'organizationEvents', orgactionitems: 'organizationActionItems', + orgagendacategory: 'organizationAgendaCategory', orgcontribution: 'orgContribution', orgpost: 'orgPost', orgfunds: 'funds', diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx new file mode 100644 index 0000000000..da92dfd201 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; + +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; + +const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', +}; +const mockHideCreateModal = jest.fn(); +const mockSetFormState = jest.fn(); +const mockCreateAgendaCategoryHandler = jest.fn(); +const mockT = (key: string): string => key; + +describe('AgendaCategoryCreateModal', () => { + test('renders modal correctly', () => { + render( + + + + + + + + + + + , + ); + + expect(screen.getByText('agendaCategoryDetails')).toBeInTheDocument(); + expect( + screen.getByTestId('createAgendaCategoryFormSubmitBtn'), + ).toBeInTheDocument(); + expect( + screen.getByTestId('createAgendaCategoryModalCloseBtn'), + ).toBeInTheDocument(); + }); + test('tests the condition for formState.name and formState.description', () => { + const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', + }; + render( + + + + + + + + + + + , + ); + const nameInput = screen.getByLabelText('name'); + fireEvent.change(nameInput, { + target: { value: 'New name' }, + }); + const descriptionInput = screen.getByLabelText('description'); + fireEvent.change(descriptionInput, { + target: { value: 'New description' }, + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + name: 'New name', + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + description: 'New description', + }); + }); + test('calls createAgendaCategoryHandler when form is submitted', () => { + render( + + + + + + + + + + + , + ); + + fireEvent.submit(screen.getByTestId('createAgendaCategoryFormSubmitBtn')); + expect(mockCreateAgendaCategoryHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx new file mode 100644 index 0000000000..9b260d877f --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import type { ChangeEvent } from 'react'; +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceFormStateType { + name: string; + description: string; + createdBy: string; +} + +interface InterfaceAgendaCategoryCreateModalProps { + agendaCategoryCreateModalIsOpen: boolean; + hideCreateModal: () => void; + formState: InterfaceFormStateType; + setFormState: (state: React.SetStateAction) => void; + createAgendaCategoryHandler: ( + e: ChangeEvent, + ) => Promise; + t: (key: string) => string; +} + +const AgendaCategoryCreateModal: React.FC< + InterfaceAgendaCategoryCreateModalProps +> = ({ + agendaCategoryCreateModalIsOpen, + hideCreateModal, + formState, + setFormState, + createAgendaCategoryHandler, + t, +}) => { + return ( + + +

{t('agendaCategoryDetails')}

+ +
+ +
+ + {t('name')} + + setFormState({ ...formState, name: e.target.value }) + } + /> + + + {t('description')} + + setFormState({ ...formState, description: e.target.value }) + } + /> + + +
+
+
+ ); +}; + +export default AgendaCategoryCreateModal; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx new file mode 100644 index 0000000000..f2edb8fc62 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Modal, Button } from 'react-bootstrap'; +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceAgendaCategoryDeleteModalProps { + agendaCategoryDeleteModalIsOpen: boolean; + toggleDeleteModal: () => void; + deleteAgendaCategoryHandler: () => Promise; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +const AgendaCategoryDeleteModal: React.FC< + InterfaceAgendaCategoryDeleteModalProps +> = ({ + agendaCategoryDeleteModalIsOpen, + toggleDeleteModal, + deleteAgendaCategoryHandler, + t, + tCommon, +}) => { + return ( + + + + {t('deleteAgendaCategory')} + + + +

{t('deleteAgendaCategoryMsg')}

+
+ + + + +
+ ); +}; + +export default AgendaCategoryDeleteModal; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx new file mode 100644 index 0000000000..c9dc17e56f --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; + +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceFormStateType { + name: string; + description: string; + createdBy: string; +} + +interface InterfaceAgendaCategoryPreviewModalProps { + agendaCategoryPreviewModalIsOpen: boolean; + hidePreviewModal: () => void; + showUpdateModal: () => void; + toggleDeleteModal: () => void; + formState: InterfaceFormStateType; + + t: (key: string) => string; +} + +const AgendaCategoryPreviewModal: React.FC< + InterfaceAgendaCategoryPreviewModalProps +> = ({ + agendaCategoryPreviewModalIsOpen, + hidePreviewModal, + showUpdateModal, + toggleDeleteModal, + formState, + t, +}) => { + return ( + + +

{t('agendaCategoryDetails')}

+ +
+ +
+
+

+ {t('name')} + {formState.name} +

+

+ {t('description')} + {formState.description} +

+

+ {t('createdBy')} + {formState.createdBy} +

+
+
+ + +
+
+
+
+ ); +}; + +export default AgendaCategoryPreviewModal; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx new file mode 100644 index 0000000000..168b97abd3 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; + +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import AgendaCategoryUpdateModal from './AgendaCategoryUpdateModal'; + +const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', +}; +const mockHideUpdateModal = jest.fn(); +const mockSetFormState = jest.fn(); +const mockUpdateAgendaCategoryHandler = jest.fn(); +const mockT = (key: string): string => key; + +describe('AgendaCategoryUpdateModal', () => { + test('renders modal correctly', () => { + render( + + + + + + + + + + + , + ); + + expect(screen.getByText('updateAgendaCategory')).toBeInTheDocument(); + expect(screen.getByTestId('editAgendaCategoryBtn')).toBeInTheDocument(); + expect( + screen.getByTestId('updateAgendaCategoryModalCloseBtn'), + ).toBeInTheDocument(); + }); + + test('calls hideUpdateModal when close button is clicked', () => { + render( + + + + + + + + + + + , + ); + + userEvent.click(screen.getByTestId('updateAgendaCategoryModalCloseBtn')); + expect(mockHideUpdateModal).toHaveBeenCalledTimes(1); + }); + + test('tests the condition for formState.name and formState.description', () => { + const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', + }; + render( + + + + + + + + + + + , + ); + const nameInput = screen.getByLabelText('name'); + fireEvent.change(nameInput, { + target: { value: 'New name' }, + }); + const descriptionInput = screen.getByLabelText('description'); + fireEvent.change(descriptionInput, { + target: { value: 'New description' }, + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + name: 'New name', + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + description: 'New description', + }); + }); + + test('calls updateAgendaCategoryHandler when form is submitted', () => { + render( + + + + + + + + + + + , + ); + + fireEvent.submit(screen.getByTestId('editAgendaCategoryBtn')); + expect(mockUpdateAgendaCategoryHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx new file mode 100644 index 0000000000..868205c339 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import type { ChangeEvent } from 'react'; + +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceFormStateType { + name: string; + description: string; + createdBy: string; +} + +interface InterfaceAgendaCategoryUpdateModalProps { + agendaCategoryUpdateModalIsOpen: boolean; + hideUpdateModal: () => void; + formState: InterfaceFormStateType; + setFormState: (state: React.SetStateAction) => void; + updateAgendaCategoryHandler: ( + e: ChangeEvent, + ) => Promise; + t: (key: string) => string; +} + +const AgendaCategoryUpdateModal: React.FC< + InterfaceAgendaCategoryUpdateModalProps +> = ({ + agendaCategoryUpdateModalIsOpen, + hideUpdateModal, + formState, + setFormState, + updateAgendaCategoryHandler, + t, +}) => { + return ( + + +

{t('updateAgendaCategory')}

+ +
+ +
+ + {t('name')} + + setFormState({ ...formState, name: e.target.value }) + } + /> + + + {t('description')} + + setFormState({ ...formState, description: e.target.value }) + } + /> + + +
+
+
+ ); +}; + +export default AgendaCategoryUpdateModal; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css new file mode 100644 index 0000000000..13e187f8b5 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css @@ -0,0 +1,171 @@ +.agendaCategoryContainer { + height: 90vh; +} + +.agendaCategoryModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.btnsContainer { + display: flex; + gap: 10px; +} + +.btnsContainer .btnsBlock { + display: flex; + gap: 10px; +} + +.btnsContainer button { + display: flex; + align-items: center; +} + +.container { + min-height: 100vh; +} + +.datediv { + display: flex; + flex-direction: row; +} + +.datebox { + width: 90%; + border-radius: 7px; + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.dropdown { + display: block; +} + +.dropdownToggle { + margin-bottom: 0; + display: flex; +} + +.dropdownModalToggle { + width: 50%; +} + +.errorIcon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.greenregbtn { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid var(--bs-gray-300); + box-shadow: 0 2px 2px var(--bs-gray-300); + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-primary); + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + width: 100%; +} + +h2 { + margin-top: 0.5rem; +} + +hr { + border: none; + height: 1px; + background-color: var(--bs-gray-500); + margin: 1rem; +} + +.iconContainer { + display: flex; + justify-content: flex-end; +} +.icon { + margin: 1px; +} + +.message { + margin-top: 25%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.organizationAgendaCategoryContainer h2 { + margin: 0.6rem 0; +} + +.preview { + display: flex; + flex-direction: row; + font-weight: 900; + font-size: 16px; + color: rgb(80, 80, 80); +} + +.removeFilterIcon { + cursor: pointer; +} + +.searchForm { + display: inline; +} + +.titlemodal { + color: var(--bs-gray-600); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--bs-primary); + width: 65%; +} + +.view { + margin-left: 2%; + font-weight: 600; + font-size: 16px; + color: var(--bs-gray-600); +} + +@media (max-width: 767px) { + .btnsContainer { + margin-bottom: 0; + display: flex; + flex-direction: column; + } + + .btnsContainer .btnsBlock .dropdownToggle { + flex-grow: 1; + } + + .btnsContainer button { + width: 100%; + } + + .createAgendaCategoryButton { + position: absolute; + top: 1rem; + right: 2rem; + } +} diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx new file mode 100644 index 0000000000..732db13a74 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx @@ -0,0 +1,235 @@ +import React from 'react'; +import { + render, + screen, + waitFor, + act, + waitForElementToBeRemoved, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import 'jest-localstorage-mock'; +import { MockedProvider } from '@apollo/client/testing'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import i18n from 'utils/i18nForTest'; +import { toast } from 'react-toastify'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import OrganizationAgendaCategory from './OrganizationAgendaCategory'; +import { + MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, + MOCKS_ERROR_MUTATION, +} from './OrganizationAgendaCategoryErrorMocks'; +import { MOCKS } from './OrganizationAgendaCategoryMocks'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: '123' }), +})); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink( + MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, + true, +); +const link3 = new StaticMockLink(MOCKS_ERROR_MUTATION, true); + +const translations = { + ...JSON.parse( + JSON.stringify( + i18n.getDataByLanguage('en')?.translation.organizationAgendaCategory ?? + {}, + ), + ), +}; + +describe('Testing Agenda Categories Component', () => { + const formData = { + name: 'Category', + description: 'Test Description', + createdBy: 'Test User', + }; + test('Component loads correctly', async () => { + const { getByText } = render( + + + + + {} + + + + , + ); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.createAgendaCategory)).toBeInTheDocument(); + }); + }); + + test('render error component on unsuccessful agenda category list query', async () => { + const { queryByText } = render( + + + + + {} + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + queryByText(translations.createAgendaCategory), + ).not.toBeInTheDocument(); + }); + }); + + test('opens and closes the create agenda category modal', async () => { + render( + + + + + + {} + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('createAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createAgendaCategoryBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('createAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('createAgendaCategoryModalCloseBtn'), + ); + }); + test('creates new agenda cagtegory', async () => { + render( + + + + + + {} + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('createAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createAgendaCategoryBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('createAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + userEvent.type( + screen.getByPlaceholderText(translations.name), + formData.name, + ); + + userEvent.type( + screen.getByPlaceholderText(translations.description), + formData.description, + ); + userEvent.click(screen.getByTestId('createAgendaCategoryFormSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.agendaCategoryCreated); + }); + }); + + // test('toasts error on unsuccessful creation', async () => { + // render( + // + // + // + // + // + // {} + // + // + // + // + // , + // ); + + // await wait(); + + // await waitFor(() => { + // expect(screen.getByTestId('createAgendaCategoryBtn')).toBeInTheDocument(); + // }); + // userEvent.click(screen.getByTestId('createAgendaCategoryBtn')); + + // await waitFor(() => { + // return expect( + // screen.findByTestId('createAgendaCategoryModalCloseBtn'), + // ).resolves.toBeInTheDocument(); + // }); + + // userEvent.type( + // screen.getByPlaceholderText(translations.name), + // formData.name, + // ); + + // userEvent.type( + // screen.getByPlaceholderText(translations.description), + // formData.description, + // ); + // userEvent.click(screen.getByTestId('createAgendaCategoryFormSubmitBtn')); + + // await waitFor(() => { + // expect(toast.error).toBeCalledWith(); + // }); + // }); +}); diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx new file mode 100644 index 0000000000..4216d309a2 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx @@ -0,0 +1,157 @@ +import React, { useState } from 'react'; +import type { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button } from 'react-bootstrap'; +import { useParams } from 'react-router-dom'; + +import { WarningAmberRounded } from '@mui/icons-material'; +import { toast } from 'react-toastify'; + +import { useMutation, useQuery } from '@apollo/client'; +import { AGENDA_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; +import { CREATE_AGENDA_ITEM_CATEGORY_MUTATION } from 'GraphQl/Mutations/mutations'; + +import type { InterfaceAgendaItemCategoryList } from 'utils/interfaces'; +import AgendaCategoryContainer from 'components/AgendaCategory/AgendaCategoryContainer'; +import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; +import styles from './OrganizationAgendaCategory.module.css'; +import Loader from 'components/Loader/Loader'; + +function organizationAgendaCategory(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationAgendaCategory', + }); + + const { orgId: currentUrl } = useParams(); + + const [agendaCategoryCreateModalIsOpen, setAgendaCategoryCreateModalIsOpen] = + useState(false); + + const [formState, setFormState] = useState({ + name: '', + description: '', + createdBy: '', + }); + + const { + data: agendaCategoryData, + loading: agendaCategoryLoading, + error: agendaCategoryError, + refetch: refetchAgendaCategory, + }: { + data: InterfaceAgendaItemCategoryList | undefined; + loading: boolean; + error?: unknown | undefined; + refetch: () => void; + } = useQuery(AGENDA_ITEM_CATEGORY_LIST, { + variables: { organizationId: currentUrl }, + notifyOnNetworkStatusChange: true, + }); + + const [createAgendaCategory] = useMutation( + CREATE_AGENDA_ITEM_CATEGORY_MUTATION, + ); + + const createAgendaCategoryHandler = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + await createAgendaCategory({ + variables: { + input: { + organizationId: currentUrl, + name: formState.name, + description: formState.description, + }, + }, + }); + toast.success(t('agendaCategoryCreated')); + setFormState({ name: '', description: '', createdBy: '' }); + refetchAgendaCategory(); + hideCreateModal(); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + const showCreateModal = (): void => { + setAgendaCategoryCreateModalIsOpen(!agendaCategoryCreateModalIsOpen); + }; + + const hideCreateModal = (): void => { + setAgendaCategoryCreateModalIsOpen(!agendaCategoryCreateModalIsOpen); + }; + + if (agendaCategoryLoading) return ; + + if (agendaCategoryError) { + return ( +
+
+ +
+ Error occured while loading{' '} + {agendaCategoryError && 'Agenda Categories'} + Data +
+ {agendaCategoryError && (agendaCategoryError as Error).message} +
+
+
+ ); + } + + return ( +
+
+
+
+
+ {/* setSearchValue(e.target.value)} + value={searchValue} + data-testid="searchAgendaCategories" + /> */} +
+ + +
+
+ +
+ + +
+ +
+ ); +} + +export default organizationAgendaCategory; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts new file mode 100644 index 0000000000..10809c3da8 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts @@ -0,0 +1,51 @@ +import { CREATE_AGENDA_ITEM_CATEGORY_MUTATION } from 'GraphQl/Mutations/AgendaCategoryMutations'; + +import { AGENDA_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/AgendaCategoryQueries'; + +export const MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY = [ + { + request: { + query: AGENDA_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + error: new Error('Mock Graphql Error'), + }, +]; + +export const MOCKS_ERROR_MUTATION = [ + { + request: { + query: AGENDA_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + result: { + data: { + agendaItemCategoriesByOrganization: [ + { + _id: 'agendaItemCategory1', + name: 'Category', + description: 'Test Description', + createdBy: { + _id: 'user1', + firstName: 'Harve', + lastName: 'Lance', + }, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + input: { + organizationId: '123', + name: 'Category', + description: 'Test Description', + }, + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts new file mode 100644 index 0000000000..434b0dcad2 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts @@ -0,0 +1,47 @@ +import { CREATE_AGENDA_ITEM_CATEGORY_MUTATION } from 'GraphQl/Mutations/AgendaCategoryMutations'; + +import { AGENDA_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/AgendaCategoryQueries'; + +export const MOCKS = [ + { + request: { + query: AGENDA_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + result: { + data: { + agendaItemCategoriesByOrganization: [ + { + _id: 'agendaItemCategory1', + name: 'Category', + description: 'Test Description', + createdBy: { + _id: 'user1', + firstName: 'Harve', + lastName: 'Lance', + }, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + input: { + organizationId: '123', + name: 'Category', + description: 'Test Description', + }, + }, + }, + result: { + data: { + createAgendaCategory: { + _id: 'agendaItemCategory1', + }, + }, + }, + }, +]; diff --git a/src/screens/Users/Users.test.tsx b/src/screens/Users/Users.test.tsx index 59e16a7878..f130ed1df9 100644 --- a/src/screens/Users/Users.test.tsx +++ b/src/screens/Users/Users.test.tsx @@ -15,13 +15,276 @@ import Users from './Users'; import { EMPTY_MOCKS, MOCKS, MOCKS2 } from './UsersMocks'; import useLocalStorage from 'utils/useLocalstorage'; +import { + USER_LIST, + ORGANIZATION_CONNECTION_LIST, +} from 'GraphQl/Queries/Queries'; + const { setItem, removeItem } = useLocalStorage(); +const MOCK_USERS = [ + { + user: { + _id: 'user1', + firstName: 'John', + lastName: 'Doe', + image: null, + email: 'john@example.com', + createdAt: '20/06/2022', + registeredEvents: [], + membershipRequests: [], + organizationsBlockedBy: [ + { + _id: 'xyz', + name: 'ABC', + image: null, + address: { + city: 'Kingston', + countryCode: 'JM', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Jamaica Street', + line2: 'Apartment 456', + postalCode: 'JM12345', + sortingCode: 'ABC-123', + state: 'Kingston Parish', + }, + createdAt: '20/06/2022', + creator: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + image: null, + email: 'john@example.com', + createdAt: '20/06/2022', + }, + }, + ], + joinedOrganizations: [ + { + _id: 'abc', + name: 'Joined Organization 1', + image: null, + address: { + city: 'Kingston', + countryCode: 'JM', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Jamaica Street', + line2: 'Apartment 456', + postalCode: 'JM12345', + sortingCode: 'ABC-123', + state: 'Kingston Parish', + }, + createdAt: '20/06/2022', + creator: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + image: null, + email: 'john@example.com', + createdAt: '20/06/2022', + }, + }, + ], + }, + appUserProfile: { + _id: 'user1', + adminFor: [ + { + _id: '123', + }, + ], + isSuperAdmin: true, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + }, + }, + { + user: { + _id: 'user2', + firstName: 'Jane', + lastName: 'Doe', + image: null, + email: 'john@example.com', + createdAt: '21/06/2022', + registeredEvents: [], + membershipRequests: [], + organizationsBlockedBy: [ + { + _id: '456', + name: 'ABC', + image: null, + address: { + city: 'Kingston', + countryCode: 'JM', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Jamaica Street', + line2: 'Apartment 456', + postalCode: 'JM12345', + sortingCode: 'ABC-123', + state: 'Kingston Parish', + }, + createdAt: '21/06/2022', + creator: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + image: null, + email: 'john@example.com', + createdAt: '21/06/2022', + }, + }, + ], + joinedOrganizations: [ + { + _id: '123', + name: 'Palisadoes', + image: null, + address: { + city: 'Kingston', + countryCode: 'JM', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Jamaica Street', + line2: 'Apartment 456', + postalCode: 'JM12345', + sortingCode: 'ABC-123', + state: 'Kingston Parish', + }, + createdAt: '21/06/2022', + creator: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + image: null, + email: 'john@example.com', + createdAt: '21/06/2022', + }, + }, + ], + }, + appUserProfile: { + _id: 'user2', + adminFor: [ + { + _id: '123', + }, + ], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + }, + }, + { + user: { + _id: 'user3', + firstName: 'Jack', + lastName: 'Smith', + image: null, + email: 'jack@example.com', + createdAt: '19/06/2022', + registeredEvents: [], + membershipRequests: [], + organizationsBlockedBy: [ + { + _id: 'xyz', + name: 'ABC', + image: null, + address: { + city: 'Kingston', + countryCode: 'JM', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Jamaica Street', + line2: 'Apartment 456', + postalCode: 'JM12345', + sortingCode: 'ABC-123', + state: 'Kingston Parish', + }, + createdAt: '19/06/2022', + creator: { + _id: '123', + firstName: 'Jack', + lastName: 'Smith', + image: null, + email: 'jack@example.com', + createdAt: '19/06/2022', + }, + }, + ], + joinedOrganizations: [ + { + _id: 'abc', + name: 'Joined Organization 1', + image: null, + address: { + city: 'Kingston', + countryCode: 'JM', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Jamaica Street', + line2: 'Apartment 456', + postalCode: 'JM12345', + sortingCode: 'ABC-123', + state: 'Kingston Parish', + }, + createdAt: '19/06/2022', + creator: { + _id: '123', + firstName: 'Jack', + lastName: 'Smith', + image: null, + email: 'jack@example.com', + createdAt: '19/06/2022', + }, + }, + ], + }, + appUserProfile: { + _id: 'user3', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + }, + }, +]; + +const MOCKS_NEW = [ + { + request: { + query: USER_LIST, + variables: { + first: 12, + skip: 0, + firstName_contains: '', + lastName_contains: '', + order: 'createdAt_DESC', + }, + }, + result: { + data: { + users: MOCK_USERS, + }, + }, + }, + { + request: { + query: ORGANIZATION_CONNECTION_LIST, + }, + result: { + data: { + organizationsConnection: [], + }, + }, + }, +]; + const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(EMPTY_MOCKS, true); const link3 = new StaticMockLink(MOCKS2, true); +const link5 = new StaticMockLink(MOCKS_NEW, true); -async function wait(ms = 100): Promise { +async function wait(ms = 1000): Promise { await act(() => { return new Promise((resolve) => { setTimeout(resolve, ms); @@ -132,6 +395,7 @@ describe('Testing Users screen', () => { userEvent.type(screen.getByTestId(/searchByName/i), search1); userEvent.click(searchBtn); await wait(); + expect(screen.queryByText(/not found/i)).not.toBeInTheDocument(); const search2 = 'Pete{backspace}{backspace}{backspace}{backspace}'; userEvent.type(screen.getByTestId(/searchByName/i), search2); @@ -152,24 +416,34 @@ describe('Testing Users screen', () => { }); test('testing search not found', async () => { - render( - - - - - - - - - , - ); + await act(async () => { + render( + + + + + + + + + , + ); - await wait(); + await wait(); - const search = 'hello{enter}'; - await act(() => - userEvent.type(screen.getByTestId(/searchByName/i), search), - ); + const searchBtn = screen.getByTestId('searchButton'); + + const searchInput = screen.getByTestId(/searchByName/i); + + // Clear the search input + userEvent.clear(searchInput); + + // Search for a name that doesn't exist + userEvent.type(screen.getByTestId(/searchByName/i), 'NonexistentName'); + userEvent.click(searchBtn); + + expect(screen.queryByText(/No User Found/i)).toBeInTheDocument(); + }); }); test('Testing User data is not present', async () => { @@ -230,42 +504,6 @@ describe('Testing Users screen', () => { ); }); - test('Testing sorting functionality', async () => { - await act(async () => { - render( - - - - - - - - - - , - ); - - await wait(); - - const searchInput = screen.getByTestId('sort'); - expect(searchInput).toBeInTheDocument(); - - const inputText = screen.getByTestId('sortUsers'); - - fireEvent.click(inputText); - const toggleText = screen.getByTestId('oldest'); - fireEvent.click(toggleText); - - expect(searchInput).toBeInTheDocument(); - - fireEvent.click(inputText); - const toggleTite = screen.getByTestId('newest'); - fireEvent.click(toggleTite); - - expect(searchInput).toBeInTheDocument(); - }); - }); - test('Testing filter functionality', async () => { await act(async () => { render( @@ -345,4 +583,132 @@ describe('Testing Users screen', () => { ); await wait(); }); + + test('should set hasMore to false if users length is less than perPageResult', async () => { + const link = new StaticMockLink(EMPTY_MOCKS, true); + + render( + + + + + + + + + + , + ); + + await wait(200); + + // Check if "No User Found" is displayed + expect(screen.getByText(/No User Found/i)).toBeInTheDocument(); + }); + + test('should filter users correctly', async () => { + await act(async () => { + render( + + + + + + + + + + , + ); + + await wait(); + + const filterButton = screen.getByTestId('filterUsers'); + fireEvent.click(filterButton); + + const filterAdmin = screen.getByTestId('admin'); + fireEvent.click(filterAdmin); + await wait(); + expect(screen.getByText('Jane Doe')).toBeInTheDocument(); + + fireEvent.click(filterButton); + const filterSuperAdmin = screen.getByTestId('superAdmin'); + fireEvent.click(filterSuperAdmin); + await wait(); + expect(screen.getByText('John Doe')).toBeInTheDocument(); + + fireEvent.click(filterButton); + const filterUser = screen.getByTestId('user'); + fireEvent.click(filterUser); + await wait(); + expect(screen.getByText('Jack Smith')).toBeInTheDocument(); + + fireEvent.click(filterButton); + const filterCancel = screen.getByTestId('cancel'); + fireEvent.click(filterCancel); + await wait(); + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Jane Doe')).toBeInTheDocument(); + expect(screen.getByText('Jack Smith')).toBeInTheDocument(); + }); + }); + + test('Users should be sorted and filtered correctly', async () => { + await act(async () => { + render( + + + + + + + + + + , + ); + + await wait(); + + // Check if the sorting and filtering logic was applied correctly + const rows = screen.getAllByRole('row'); + + const firstRow = rows[1]; + const secondRow = rows[2]; + + expect(firstRow).toHaveTextContent('John Doe'); + expect(secondRow).toHaveTextContent('Jane Doe'); + + await wait(); + + const inputText = screen.getByTestId('sortUsers'); + + fireEvent.click(inputText); + const toggleText = screen.getByTestId('oldest'); + fireEvent.click(toggleText); + + fireEvent.click(inputText); + const toggleTite = screen.getByTestId('newest'); + fireEvent.click(toggleTite); + + // Verify the users are sorted by oldest + await wait(); + + const displayedUsers = screen.getAllByRole('row'); + expect(displayedUsers[1]).toHaveTextContent('John Doe'); // assuming User1 is the oldest + expect(displayedUsers[displayedUsers.length - 1]).toHaveTextContent( + 'Jack Smith', + ); // assuming UserN is the newest + + await wait(); + + fireEvent.click(inputText); + const toggleOld = screen.getByTestId('oldest'); + fireEvent.click(toggleOld); + + fireEvent.click(inputText); + const toggleNewest = screen.getByTestId('newest'); + fireEvent.click(toggleNewest); + }); + }); }); diff --git a/src/screens/Users/Users.tsx b/src/screens/Users/Users.tsx index c03842069f..3dae6ec17e 100644 --- a/src/screens/Users/Users.tsx +++ b/src/screens/Users/Users.tsx @@ -18,6 +18,7 @@ import InfiniteScroll from 'react-infinite-scroll-component'; import type { InterfaceQueryUserListItem } from 'utils/interfaces'; import styles from './Users.module.css'; import useLocalStorage from 'utils/useLocalstorage'; +import type { ApolloError } from '@apollo/client'; const Users = (): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'users' }); @@ -34,43 +35,52 @@ const Users = (): JSX.Element => { const [searchByName, setSearchByName] = useState(''); const [sortingOption, setSortingOption] = useState('newest'); const [filteringOption, setFilteringOption] = useState('cancel'); - const superAdmin = getItem('SuperAdmin'); - const adminFor = getItem('AdminFor'); - const userRole = superAdmin + const userType = getItem('SuperAdmin') ? 'SUPERADMIN' - : adminFor?.length > 0 + : getItem('AdminFor') ? 'ADMIN' : 'USER'; - const loggedInUserId = getItem('id'); - const { data, loading, fetchMore, refetch } = useQuery(USER_LIST, { + const { + data: usersData, + loading: loading, + fetchMore, + refetch: refetchUsers, + }: { + data?: { users: InterfaceQueryUserListItem[] }; + loading: boolean; + fetchMore: any; + refetch: any; + error?: ApolloError; + } = useQuery(USER_LIST, { variables: { first: perPageResult, skip: 0, firstName_contains: '', lastName_contains: '', + order: sortingOption === 'newest' ? 'createdAt_DESC' : 'createdAt_ASC', }, notifyOnNetworkStatusChange: true, }); const { data: dataOrgs } = useQuery(ORGANIZATION_CONNECTION_LIST); - const [displayedUsers, setDisplayedUsers] = useState(data?.users || []); + const [displayedUsers, setDisplayedUsers] = useState(usersData?.users || []); // Manage loading more state useEffect(() => { - if (!data) { + if (!usersData) { return; } - if (data.users.length < perPageResult) { + if (usersData.users.length < perPageResult) { setHasMore(false); } - if (data && data.users) { - let newDisplayedUsers = sortUsers(data.users, sortingOption); + if (usersData && usersData.users) { + let newDisplayedUsers = sortUsers(usersData.users, sortingOption); newDisplayedUsers = filterUsers(newDisplayedUsers, filteringOption); setDisplayedUsers(newDisplayedUsers); } - }, [data, sortingOption, filteringOption]); + }, [usersData, sortingOption, filteringOption]); // To clear the search when the component is unmounted useEffect(() => { @@ -92,7 +102,7 @@ const Users = (): JSX.Element => { // Send to orgList page if user is not superadmin useEffect(() => { - if (userRole != 'SUPERADMIN') { + if (userType != 'SUPERADMIN') { window.location.assign('/orglist'); } }, []); @@ -112,18 +122,17 @@ const Users = (): JSX.Element => { resetAndRefetch(); return; } - refetch({ + refetchUsers({ firstName_contains: value, lastName_contains: '', // Later on we can add several search and filter options }); + setHasMore(true); }; - const handleSearchByEnter = ( - e: React.KeyboardEvent, - ): void => { + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { - const { value } = e.currentTarget; + const { value } = e.target; handleSearch(value); } }; @@ -137,11 +146,12 @@ const Users = (): JSX.Element => { }; /* istanbul ignore next */ const resetAndRefetch = (): void => { - refetch({ + refetchUsers({ first: perPageResult, skip: 0, firstName_contains: '', lastName_contains: '', + order: sortingOption === 'newest' ? 'createdAt_DESC' : 'createdAt_ASC', }); setHasMore(true); }; @@ -150,8 +160,10 @@ const Users = (): JSX.Element => { setIsLoadingMore(true); fetchMore({ variables: { - skip: data?.users.length || 0, + skip: usersData?.users.length || 0, + userType: 'ADMIN', filter: searchByName, + order: sortingOption === 'newest' ? 'createdAt_DESC' : 'createdAt_ASC', }, updateQuery: ( prev: { users: InterfaceQueryUserListItem[] } | undefined, @@ -174,6 +186,8 @@ const Users = (): JSX.Element => { }; const handleSorting = (option: string): void => { + setDisplayedUsers([]); + setHasMore(true); setSortingOption(option); }; @@ -201,6 +215,7 @@ const Users = (): JSX.Element => { }; const handleFiltering = (option: string): void => { + setDisplayedUsers([]); setFilteringOption(option); }; @@ -249,7 +264,7 @@ const Users = (): JSX.Element => {
{
{isLoading == false && - data && + usersData && displayedUsers.length === 0 && searchByName.length > 0 ? (
@@ -350,7 +365,9 @@ const Users = (): JSX.Element => { {tCommon('noResultsFoundFor')} "{searchByName}"
- ) : isLoading == false && data && displayedUsers.length === 0 ? ( + ) : isLoading == false && + usersData === undefined && + displayedUsers.length === 0 ? (

{t('noUserFound')}

@@ -393,7 +410,7 @@ const Users = (): JSX.Element => { - {data && + {usersData && displayedUsers.map( (user: InterfaceQueryUserListItem, index: number) => { return ( diff --git a/src/state/reducers/routesReducer.test.ts b/src/state/reducers/routesReducer.test.ts index 0a474df0c3..84427ee4d0 100644 --- a/src/state/reducers/routesReducer.test.ts +++ b/src/state/reducers/routesReducer.test.ts @@ -16,6 +16,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/undefined' }, { name: 'Venues', url: '/orgvenues/undefined' }, { name: 'Action Items', url: '/orgactionitems/undefined' }, + { name: 'Agenda Items Category', url: '/orgagendacategory/undefined' }, { name: 'Posts', url: '/orgpost/undefined' }, { name: 'Block/Unblock', @@ -63,6 +64,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { @@ -113,6 +119,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/orgId' }, { name: 'Venues', url: '/orgvenues/orgId' }, { name: 'Action Items', url: '/orgactionitems/orgId' }, + { name: 'Agenda Items Category', url: '/orgagendacategory/orgId' }, { name: 'Posts', url: '/orgpost/orgId' }, { name: 'Block/Unblock', url: '/blockuser/orgId' }, { name: 'Advertisement', url: '/orgads/orgId' }, @@ -157,6 +164,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { @@ -203,6 +215,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/undefined' }, { name: 'Venues', url: '/orgvenues/undefined' }, { name: 'Action Items', url: '/orgactionitems/undefined' }, + { name: 'Agenda Items Category', url: '/orgagendacategory/undefined' }, { name: 'Posts', url: '/orgpost/undefined' }, { name: 'Block/Unblock', @@ -253,6 +266,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { diff --git a/src/state/reducers/routesReducer.ts b/src/state/reducers/routesReducer.ts index 34cfdbbcca..bcda9f02d8 100644 --- a/src/state/reducers/routesReducer.ts +++ b/src/state/reducers/routesReducer.ts @@ -76,6 +76,11 @@ const components: ComponentType[] = [ comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { name: 'Advertisement', comp_id: 'orgads', component: 'Advertisements' }, diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index abaac2efcc..1d65b63189 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -456,3 +456,18 @@ export interface InterfaceQueryMembershipRequestsListItem { }[]; }[]; } + +export interface InterfaceAgendaItemCategoryInfo { + _id: string; + name: string; + description: string; + createdBy: { + _id: string; + firstName: string; + lastName: string; + }; +} + +export interface InterfaceAgendaItemCategoryList { + agendaItemCategoriesByOrganization: InterfaceAgendaItemCategoryInfo[]; +}