diff --git a/package-lock.json b/package-lock.json index 92d3694c74409f82311aa05598136553f5f7f481..f1b75d87940e720416118ac0d5436694b0dca063 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,11 +85,13 @@ "react-markdown": "^9.0.1", "react-organizational-chart": "^2.2.1", "react-phone-number-input": "^3.4.3", + "recharts": "^2.12.7", "rehype-highlight": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "simplebar-react": "^3.2.5", "sonner": "^1.5.0", + "styled-components": "^6.1.12", "stylis": "^4.3.2", "stylis-plugin-rtl": "^2.1.1", "swr": "^2.2.5", @@ -8017,6 +8019,69 @@ "integrity": "sha512-wy2y/2hQKrS6myOS++koXg3N1Hg+LLyPjaggCFajczSHZPqBnOMuT2sdH3kiASrmdBYyM3pmjyz5SoWraRllCQ==", "license": "MIT" }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -10654,6 +10719,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/can-use-dom": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz", @@ -11068,6 +11142,15 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", "license": "MIT" }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -11084,6 +11167,17 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -11161,6 +11255,127 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -11257,6 +11472,12 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -12779,6 +13000,15 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -13922,6 +14152,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -17667,6 +17906,21 @@ "react-dom": ">=16.8" } }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -17717,6 +17971,44 @@ "node": ">= 12.13.0" } }, + "node_modules/recharts": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -18256,6 +18548,12 @@ "sha.js": "bin.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -18697,6 +18995,89 @@ "inline-style-parser": "0.2.3" } }, + "node_modules/styled-components": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz", + "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/styled-components/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -18975,6 +19356,12 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tiny-lru": { "version": "11.2.11", "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.11.tgz", @@ -19795,6 +20182,28 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/viem": { "version": "1.21.4", "resolved": "https://registry.npmjs.org/viem/-/viem-1.21.4.tgz", diff --git a/package.json b/package.json index bce3b1a553d2b4cfdcf820949c35928d01dfd357..416d18804fdd0fe82a74bddc06dbfbba77fc1e4f 100644 --- a/package.json +++ b/package.json @@ -99,11 +99,13 @@ "react-markdown": "^9.0.1", "react-organizational-chart": "^2.2.1", "react-phone-number-input": "^3.4.3", + "recharts": "^2.12.7", "rehype-highlight": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "simplebar-react": "^3.2.5", "sonner": "^1.5.0", + "styled-components": "^6.1.12", "stylis": "^4.3.2", "stylis-plugin-rtl": "^2.1.1", "swr": "^2.2.5", diff --git a/public/assets/images/events/AIShowcase-463w.png b/public/assets/images/events/AIShowcase-463w.png new file mode 100644 index 0000000000000000000000000000000000000000..b21713cc7f01296216385bc48a704530df1445b5 Binary files /dev/null and b/public/assets/images/events/AIShowcase-463w.png differ diff --git a/public/assets/images/events/Keynote-463w.png b/public/assets/images/events/Keynote-463w.png new file mode 100644 index 0000000000000000000000000000000000000000..d3c9c788faecba97ac8f3057f264a3be3ab9fde5 Binary files /dev/null and b/public/assets/images/events/Keynote-463w.png differ diff --git a/public/assets/images/events/speakerphoto-463w.png b/public/assets/images/events/speakerphoto-463w.png new file mode 100644 index 0000000000000000000000000000000000000000..4cfbabebd8c93afdd51bade337e9d89717b5afff Binary files /dev/null and b/public/assets/images/events/speakerphoto-463w.png differ diff --git a/public/assets/images/events/speakerphoto_3-463w.png b/public/assets/images/events/speakerphoto_3-463w.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d56dca78b6f609b59439403404ec567d0091ab Binary files /dev/null and b/public/assets/images/events/speakerphoto_3-463w.png differ diff --git a/public/assets/images/events/speakerphoto_3-a136913b-463w.png b/public/assets/images/events/speakerphoto_3-a136913b-463w.png new file mode 100644 index 0000000000000000000000000000000000000000..c42bf255ea75897117944d0094035c19aff293b8 Binary files /dev/null and b/public/assets/images/events/speakerphoto_3-a136913b-463w.png differ diff --git a/src/app/dashboard/events/Administration/campagnes/[id]/page.tsx b/src/app/dashboard/events/Administration/campagnes/[id]/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b3771a320a715e5fc101b78e6413779e9608cc5e --- /dev/null +++ b/src/app/dashboard/events/Administration/campagnes/[id]/page.tsx @@ -0,0 +1,15 @@ +import { CONFIG } from 'src/config-global'; +import { CampagneHome } from 'src/shared/sections/events/campagnes/view/view'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +type Props = { + params: {id: number }; +}; +export default function Page({ params }: Props) { + const id = params; + + return <CampagneHome eventId={id} />; +} diff --git a/src/app/dashboard/events/Administration/campagnes/page.tsx b/src/app/dashboard/events/Administration/campagnes/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5b2871aaa3c275f1d6503658f09a465f33e673fb --- /dev/null +++ b/src/app/dashboard/events/Administration/campagnes/page.tsx @@ -0,0 +1,11 @@ +import { CONFIG } from 'src/config-global'; + +import { CampagneList } from 'src/shared/sections/events/campagnes/campagneList'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +export default function Page() { + return <CampagneList title="Liste de campagnes" />; +} diff --git a/src/app/dashboard/events/Administration/hackathons/[id]/competition/page.tsx b/src/app/dashboard/events/Administration/hackathons/[id]/competition/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c1e8bf61c35ad46f4f16586bfb61eb53cbe801da --- /dev/null +++ b/src/app/dashboard/events/Administration/hackathons/[id]/competition/page.tsx @@ -0,0 +1,10 @@ +import { CONFIG } from 'src/config-global'; +import CompetitionPage from 'src/shared/sections/events/hackathons/view/competition'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +export default function Page() { + return <CompetitionPage />; +} diff --git a/src/app/dashboard/events/Administration/hackathons/[id]/page.tsx b/src/app/dashboard/events/Administration/hackathons/[id]/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b7c42bd9f39864f124f3fbb4098efb960a7d3372 --- /dev/null +++ b/src/app/dashboard/events/Administration/hackathons/[id]/page.tsx @@ -0,0 +1,18 @@ +import { CONFIG } from 'src/config-global'; +import { HackthonHome } from 'src/shared/sections/events/hackathons/view/view'; + +import { Event, eventsList, Session } from 'src/shared/_mock/_event'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +type Props = { + params: {id: number }; +}; + +export default function Page({ params }: Props) { + const id = params; + + return <HackthonHome eventId={id} />; +} diff --git a/src/app/dashboard/events/Administration/hackathons/page.tsx b/src/app/dashboard/events/Administration/hackathons/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..951278378eb5863286b1e3eec1acd2814208a2a6 --- /dev/null +++ b/src/app/dashboard/events/Administration/hackathons/page.tsx @@ -0,0 +1,10 @@ +import { CONFIG } from 'src/config-global'; +import { HackathonList } from 'src/shared/sections/events/hackathons/hackathonList'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +export default function Page() { + return <HackathonList />; +} diff --git a/src/app/dashboard/events/Administration/rooms/[id]/page.tsx b/src/app/dashboard/events/Administration/rooms/[id]/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e6c4eda9b4ec40de7cbfe309bdf32b2ee247df38 --- /dev/null +++ b/src/app/dashboard/events/Administration/rooms/[id]/page.tsx @@ -0,0 +1,19 @@ + +import { CONFIG } from 'src/config-global'; +import View from 'src/shared/sections/events/room/view/view'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Salon | Dashboard - ${CONFIG.site.name}` }; + +type Props = { + params: {id: string }; +}; + +export default function Page({ params }: Props) { + const {id} = params; + // const currentRoom = rooms[id]; + return ( + <View/> + ); +} diff --git a/src/app/dashboard/events/Administration/rooms/page.tsx b/src/app/dashboard/events/Administration/rooms/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dda4443e69ed25fe6217efc4d50b4f34ef5c2581 --- /dev/null +++ b/src/app/dashboard/events/Administration/rooms/page.tsx @@ -0,0 +1,11 @@ +import { CONFIG } from 'src/config-global'; +import { Rooms } from 'src/shared/sections/events/room/rooms-administration'; + + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +export default function Page() { + return <Rooms />; +} diff --git a/src/app/dashboard/events/page.tsx b/src/app/dashboard/events/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f0b15d29fc36be7dd44c5b00de2001009ae93468 --- /dev/null +++ b/src/app/dashboard/events/page.tsx @@ -0,0 +1,11 @@ +import { CONFIG } from 'src/config-global'; + +import { EventList } from 'src/shared/sections/events/eventList'; + +// ---------------------------------------------------------------------- + +export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; + +export default function Page() { + return <EventList title="Événements" />; +} diff --git a/src/routes/paths.ts b/src/routes/paths.ts index 58702919fd4f2570b58d83f7883d5a20394bd83e..84100b82fc8a33226c97963dac2f1182267b95bc 100644 --- a/src/routes/paths.ts +++ b/src/routes/paths.ts @@ -48,11 +48,15 @@ export const paths = { }, }, // DASHBOARD + dashboard: { root: ROOTS.DASHBOARD, blank: `${ROOTS.DASHBOARD}/blank`, nftsmartcontract: `${ROOTS.DASHBOARD}/nftsmartcontract`, tokensmartcontract: `${ROOTS.DASHBOARD}/tokensmartcontract`, + hackathons:`${ROOTS.DASHBOARD}/events/Administration/hackathons`, + campagnes:`${ROOTS.DASHBOARD}/events/Administration/campagnes`, + rooms:`${ROOTS.DASHBOARD}/events/Administration/rooms`, job: { root: `${ROOTS.DASHBOARD}/job`, new: `${ROOTS.DASHBOARD}/job/new`, @@ -100,6 +104,9 @@ export const paths = { stats: { root: `${ROOTS.STATS}`, }, + events: { + root: `${ROOTS.DASHBOARD}/events`, + }, recruiter: { root: `${ROOTS.DASHBOARD}${ROOTS.RECRUITER}`, }, diff --git a/src/shared/_mock/_event.ts b/src/shared/_mock/_event.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6137a54f00bfee1a79b1d7ce317c67a7535d3fa --- /dev/null +++ b/src/shared/_mock/_event.ts @@ -0,0 +1,457 @@ +export interface Event { + id: number; + name: string; + startDate: string; + endDate: string; + coverImage: string; + shortDescription: string; + duration: string; + activities: string; + type: 'hackathon' | 'campagne'; + visibility: 'public' | 'private'; + sessions?: Session[]; + speakers?: Speaker[]; +} + +export interface Speaker { + id: number; + name: string; + role: string; + linkedin: string; + imageUrl: string; +} + +export interface Session { + description: string; + id: number; + title: string; + time: string; + speaker: Speaker | null; + roomLink: string; +} + +export const speakers: Speaker[] = [ + { + id: 1, + name: 'Vivek Ravisankar', + role: 'Co-founder & CEO at HackerRank', + linkedin: 'https://www.linkedin.com/in/vivek-ravisankar', + imageUrl: '/assets/images/events/Keynote-463w.png', + }, + { + id: 2, + name: 'Beyang Liu', + role: 'Co-founder & CTO at Sourcegraph', + linkedin: 'https://www.linkedin.com/in/beyang-liu', + imageUrl: '/assets/images/events/speakerphoto-463w.png', + }, + { + id: 3, + name: 'Vinod Khosla', + role: 'Founder at Khosla Ventures', + linkedin: 'https://www.linkedin.com/in/vinod-khosla', + imageUrl: '/assets/images/events/speakerphoto_3-463w.png', + }, + { + id: 4, + name: 'Josh Bersin', + role: 'HR Visionary', + linkedin: 'https://www.linkedin.com/in/josh-bersin', + imageUrl: '/assets/images/events/speakerphoto_3-a136913b-463w.png', + }, + { + id: 5, + name: 'Vivek Ravisankar', + role: 'Co-founder & CEO at HackerRank', + linkedin: 'https://www.linkedin.com/in/vivek-ravisankar', + imageUrl: '/assets/images/events/Keynote-463w.png', + }, + { + id: 6, + name: 'Beyang Liu', + role: 'Co-founder & CTO at Sourcegraph', + linkedin: 'https://www.linkedin.com/in/beyang-liu', + imageUrl: '/assets/images/events/speakerphoto-463w.png', + }, + { + id: 7, + name: 'Vinod Khosla', + role: 'Founder at Khosla Ventures', + linkedin: 'https://www.linkedin.com/in/vinod-khosla', + imageUrl: '/assets/images/events/speakerphoto_3-463w.png', + }, + { + id: 8, + name: 'Josh Bersin', + role: 'HR Visionary', + linkedin: 'https://www.linkedin.com/in/josh-bersin', + imageUrl: '/assets/images/events/speakerphoto_3-a136913b-463w.png', + }, +]; + +export const eventsList: Event[] = [ + { + id: 1, + name: 'Hackathon Techs 2024', + startDate: '2024-09-01', + endDate: '2024-09-03', + coverImage: '/assets/images/events/hackathon2024.jpg', + shortDescription: 'Un hackathon de 48 heures pour innover dans le domaine des technologies.', + duration: '48 heures', + activities: 'Codage, ateliers, réseautage', + type: 'hackathon', + visibility: 'public', + sessions: [ + { + id: 1, + title: 'Discours d\'ouverture', + time: '2024-09-01T09:00:00', + speaker: speakers[0], + roomLink: 'https://example.com/session/discours-ouverture', + description: 'Description... Description...', + }, + { + id: 2, + title: 'Atelier sur l\'IA', + time: '2024-09-01T11:00:00', + speaker: speakers[1], + roomLink: 'https://example.com/session/atelier-ia', + description: 'Description... Description...', + }, + ], + speakers, + }, + { + id: 2, + name: 'Progression du Recrutement 2024', + startDate: '2024-10-10', + endDate: '2024-10-12', + coverImage: '/assets/images/events/recrutement2024.jpg', + shortDescription: 'Améliorer les processus de recrutement à travers la technologie.', + duration: '48 heures', + activities: 'Ateliers, panels de discussion, réseautage', + type: 'hackathon', + visibility: 'private', + sessions: [ + { + id: 3, + title: 'Optimisation du recrutement', + time: '2024-10-10T10:00:00', + speaker: speakers[2], + roomLink: 'https://example.com/session/optimisation-recrutement', + description: 'Description... Description...', + }, + ], + speakers, + }, + { + id: 3, + name: 'Industrie 4.x - Hackathon 2024', + startDate: '2024-11-15', + endDate: '2024-11-17', + coverImage: '/assets/images/events/industrie4x.jpg', + shortDescription: 'Innover pour l\'industrie du futur avec les technologies de l\'industrie 4.x.', + duration: '48 heures', + activities: 'Codage, ateliers, réseautage', + type: 'hackathon', + visibility: 'public', + sessions: [ + { + id: 4, + title: 'Conférence sur l\'industrie 4.x', + time: '2024-11-15T14:00:00', + speaker: speakers[3], + roomLink: 'https://example.com/session/conference-industrie4x', + description: 'Description... Description...', + }, + ], + speakers, + }, + { + id: 4, + name: 'Vision et Mentalités de Travail 2024', + startDate: '2024-12-05', + endDate: '2024-12-07', + coverImage: '/assets/images/events/vision-mentalites.jpg', + shortDescription: 'Encourager une meilleure vision et des mentalités de travail pour l\'avenir.', + duration: '48 heures', + activities: 'Ateliers, discussions, réseautage', + type: 'hackathon', + visibility: 'private', + sessions: [ + { + id: 5, + title: 'Atelier sur les nouvelles mentalités de travail', + time: '2024-12-05T10:00:00', + speaker: speakers[4], + roomLink: 'https://example.com/session/atelier-mentalites-travail', + description: 'Description... Description...', + }, + ], + speakers, + }, + { + id: 5, + name: 'Hackathon Intelligence Artificielle', + startDate: '2024-07-20', + endDate: '2024-07-22', + coverImage: '/assets/images/events/hackathon-ia.jpg', + shortDescription: 'Développer des solutions d\'IA pour le futur.', + duration: '48 heures', + activities: 'Codage, ateliers, réseautage', + type: 'hackathon', + visibility: 'private', + sessions: [ + { + id: 6, + title: 'Conférence sur l\'IA éthique', + time: '2024-07-20T10:00:00', + speaker: speakers[5], + roomLink: 'https://example.com/session/conference-ia-ethique', + description: 'Description... Description...', + }, + ], + speakers, + }, + { + id: 6, + name: 'Progression du Recrutement - Campagne 2024', + startDate: '2024-06-01', + endDate: '2024-06-30', + coverImage: '/assets/images/events/campagne-recrutement.jpg', + shortDescription: 'Une campagne pour améliorer les processus de recrutement.', + duration: '1 mois', + activities: 'Ateliers, formations, conférences', + type: 'campagne', + visibility: 'public', + speakers: [], + }, + { + id: 7, + name: 'Industrie 4.x - Campagne de Sensibilisation', + startDate: '2024-03-01', + endDate: '2024-03-31', + coverImage: '/assets/images/events/campagne-industrie4x.jpg', + shortDescription: 'Sensibilisation aux technologies de l\'industrie 4.x.', + duration: '1 mois', + activities: 'Conférences, ateliers, discussions', + type: 'campagne', + visibility: 'private', + speakers: [], + }, + { + id: 8, + name: 'Vision et Mentalités de Travail - Campagne 2024', + startDate: '2024-04-22', + endDate: '2024-05-22', + coverImage: '/assets/images/events/campagne-vision-mentalites.jpg', + shortDescription: 'Encourager une vision de travail améliorée et des mentalités de travail modernes.', + duration: '1 mois', + activities: 'Ateliers, conférences, discussions', + type: 'campagne', + visibility: 'public', + speakers: [], + }, + { + id: 9, + name: 'Hackathon Innovation 2024', + startDate: '2024-12-10', + endDate: '2024-12-12', + coverImage: '/assets/images/events/hackathon-innovation.jpg', + shortDescription: 'Innover pour résoudre les défis mondiaux.', + duration: '48 heures', + activities: 'Codage, ateliers, réseautage', + type: 'hackathon', + visibility: 'public', + sessions: [ + { + id: 7, + title: 'Pitch final', + time: '2024-12-12T15:00:00', + speaker: speakers[6], + roomLink: 'https://example.com/session/pitch-final', + description: 'Description... Description...', + }, + ], + speakers, + }, +]; + + +export const participantStatusData = [ + { name: 'Participants', value: 60 }, + { name: 'Demandes en attente', value: 20 }, + { name: 'Demandes rejetées', value: 10 }, +]; + +export const userTypesData = [ + { type: 'Utilisateur Standard', value: 40 }, + { type: 'Freelance', value: 30 }, + { type: 'Recruteur', value: 20 }, +]; + +export const activityDistributionData = [ + { activity: 'Sessions', value: 50 }, + { activity: 'Forum de discussion', value: 30 }, + { activity: 'Hackathon', value: 20 }, +]; + +export const sessionsParticipationData = [ + { session: 'Session 1', participants: 120 }, + { session: 'Session 2', participants: 80 }, + { session: 'Session 3', participants: 60 }, + { session: 'Session 4', participants: 100 }, +]; + +export const userFeedbackData = [ + { rating: '1 étoile', count: 10 }, + { rating: '2 étoiles', count: 20 }, + { rating: '3 étoiles', count: 40 }, + { rating: '4 étoiles', count: 30 }, + { rating: '5 étoiles', count: 50 }, +]; + +export const registrationConversionData = [ + { name: 'Inscriptions validées', value: 60 }, + { name: 'Demandes d’inscriptions reçues', value: 100 }, +]; + +export const feedbackList = [ + { username: '@john_doe', comment: 'Great event!', rating: 5 }, + { username: '@jane_smith', comment: 'Could be improved.', rating: 3 }, + { username: '@bob_brown', comment: 'Loved it!', rating: 4 }, +]; + +export const participantList = [ + { username: '@john_doe', status: 'participant' }, + { username: '@jane_smith', status: 'demande' }, + { username: '@bob_brown', status: 'participant' }, +]; + +export const registrationData = [ + { name: 'Inscriptions validées', value: 60 }, + { name: 'Demandes d’inscriptions reçues', value: 100 }, +]; + +export const feedbackData = [ + { name: '1 étoile', value: 5 }, + { name: '2 étoiles', value: 10 }, + { name: '3 étoiles', value: 30 }, + { name: '4 étoiles', value: 25 }, + { name: '5 étoiles', value: 30 }, +]; + +export const projectProgressData = [ + { name: 'Projets soumis', value: 40 }, + { name: 'Projets affectés', value: 60 }, +]; + +export const workshopParticipationData = [ + { subject: 'Atelier 1', A: 120, fullMark: 150 }, + { subject: 'Atelier 2', A: 98, fullMark: 150 }, + { subject: 'Atelier 3', A: 86, fullMark: 150 }, + { subject: 'Atelier 4', A: 99, fullMark: 150 }, + { subject: 'Atelier 5', A: 85, fullMark: 150 }, +]; + +export const hackathonFeedbackData = [ + { subject: '1 étoile', A: 10, fullMark: 150 }, + { subject: '2 étoiles', A: 15, fullMark: 150 }, + { subject: '3 étoiles', A: 40, fullMark: 150 }, + { subject: '4 étoiles', A: 30, fullMark: 150 }, + { subject: '5 étoiles', A: 55, fullMark: 150 }, +]; + + +export interface DeroulementItem { + id: number; + title: string; + description: string; + image: string; +} + +export const deroulementItems: DeroulementItem[] = [ + { id: 1, title: 'AI Innovation Showcase by Hire-3', description: 'Experience AI’s impact on coding and recruitment through an engaging showcase led by Hire-3 Product Leadership Team, highlighting AI’s potential to enhance coding skills and efficiency.', image: '/assets/images/events/AIShowcase-463w.png'}, + { id: 2, title: 'Market Insights with Josh Bersin, HR Visionary', description: 'Explore the intersection of AI, HR technology, and the workforce with HR expert Josh Bersin, discussing strategies for navigating future labor markets.', image: '/assets/images/events/speakerphoto_3-a136913b-463w.png' }, + ]; + + + export interface Message { + id: number; + username: string; + profileImage: string; + text: string; + reactions: { [key: string]: number }; + } + + export interface SubDiscussion { + id: number; + title: string; + members: string[]; + } + + export type ShowModalType = boolean | 'project'; + export type Emoji = string; + + export const dummyMessages: Message[] = [ + { id: 1, username: 'user1', profileImage: 'https://via.placeholder.com/30', text: 'Hello, this is a message.', reactions: {} }, + { id: 2, username: 'user2', profileImage: 'https://via.placeholder.com/30', text: 'Another message here.', reactions: { 'ðŸ‘': 1 } }, + ]; + + export const dummySubDiscussions: SubDiscussion[] = [ + { id: 1, title: 'Sub-discussion 1', members: ['user1', 'user2'] }, + { id: 2, title: 'Sub-discussion 2', members: ['user1'] }, + ]; + + export const dummyGuide = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Pellentesque vitae nisi et diam euismod malesuada.`; + export const dummyTests = [{ title: 'Test 1' }, { title: 'Test 2' }]; + + export const emojiList: Emoji[] = ['ðŸ‘', 'â¤ï¸', '😂', '😮', '😢', '😡']; + + + + export interface Member { + username: string; + profileImage: string; +} + +export interface Team { + name: string; + flag: string; + members: Member[]; + guide: string | null; + tests: Test[]; +} + +export interface Test { + title: string; +} + +export const teamsData: Team[] = [ + { + name: 'Équipe A', + flag: 'https://via.placeholder.com/30', + members: [ + { username: 'user1', profileImage: 'https://via.placeholder.com/30' }, + { username: 'user2', profileImage: 'https://via.placeholder.com/30' }, + ], + guide: null, + tests: [], + }, + { + name: 'Équipe B', + flag: 'https://via.placeholder.com/30', + members: [ + { username: 'user3', profileImage: 'https://via.placeholder.com/30' }, + { username: 'user4', profileImage: 'https://via.placeholder.com/30' }, + ], + guide: null, + tests: [], + }, +]; + +export const testsData: Test[] = [ + { title: 'Test 1' }, + { title: 'Test 2' }, +]; \ No newline at end of file diff --git a/src/shared/_mock/_room.ts b/src/shared/_mock/_room.ts new file mode 100644 index 0000000000000000000000000000000000000000..c65daef6c5299065263089259c37efa16fce3a69 --- /dev/null +++ b/src/shared/_mock/_room.ts @@ -0,0 +1,63 @@ +// src/shared/sections/events/room/types.ts + +// Types for room and user +export interface Room { + id: string; + title: string; + speakers: string[]; + status: 'live' | 'ended' | 'en attente'; + date: string; + time: string; + participationCriteria: string; + } + +export interface User { + id: string; + role: 'admin' | 'user 1'| 'user 2'; + name: string; + permissions: { + camera: boolean; + screenSharing: boolean; + microphone: boolean; + }; + isScreenSharing: boolean; + isActive: boolean; + isCameraActive: boolean; + isMicActive: boolean; + } + + export interface ChatMessage { + id: number; + userId: string; + message: string; + } + export const sampleRooms: Room[] = [ + { + id: 'fbodufjjgjlgmd', + title: 'Room 1', + speakers: ['Speaker 1', 'Speaker 2'], + status: 'live', + date: '2024-08-18', + time: '14:00', + participationCriteria: 'Open', + }, + { + id: 'gfffffffffffhhhdnh', + title: 'Room 2', + speakers: ['Speaker 3'], + status: 'ended', + date: '2024-08-17', + time: '15:00', + participationCriteria: 'By Invite Only', + }, + { + id: 'dggtgbhbgf', + title: 'Room 3', + speakers: ['Speaker 4'], + status: 'en attente', + date: '2024-08-19', + time: '16:00', + participationCriteria: 'Open', + }, + ]; + \ No newline at end of file diff --git a/src/shared/components/event/campagneDashboard.tsx b/src/shared/components/event/campagneDashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a846001290d37a0625b4b6ef668602a620746791 --- /dev/null +++ b/src/shared/components/event/campagneDashboard.tsx @@ -0,0 +1,397 @@ +import React, { useState } from 'react'; +import { + PieChart, Pie, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, Legend, Tooltip, ResponsiveContainer, Cell +} from 'recharts'; +import { Box, Typography, Button, Modal, IconButton, Switch } from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import styled from 'styled-components'; +import {participantStatusData , userTypesData , activityDistributionData ,sessionsParticipationData, userFeedbackData, registrationConversionData, feedbackList ,participantList + } from 'src/shared/_mock/_event' + +// Light and dark mode styles +const lightModeStyles = { + backgroundColor: '#fff', + color: '#000', + buttonBackgroundColor: '#f0f0f0', + buttonHoverColor: '#e0e0e0', + modalBackgroundColor: '#fff', + modalBorderColor: '#ddd', +}; + +const darkModeStyles = { + backgroundColor: '#121212', + color: '#e0e0e0', + buttonBackgroundColor: '#1f1f1f', + buttonHoverColor: '#333333', + modalBackgroundColor: '#1e1e1e', + modalBorderColor: '#333', +}; + +// Styled components with props for dynamic styling +const DashboardContainer = styled(Box)` + padding: 16px; + background-color: ${(props) => props.theme.backgroundColor}; + color: ${(props) => props.theme.color}; +`; + +const GraphTitle = styled(Typography)` + margin-top: 16px; + color: ${(props) => props.theme.color}; +`; + +const GraphContainer = styled(Box)` + position: relative; + margin-top: 16px; +`; + +const StyledButton = styled(Button)` + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + background-color: ${(props) => props.theme.buttonBackgroundColor}; + color: ${(props) => props.theme.color}; + + &:hover { + background-color: ${(props) => props.theme.buttonHoverColor}; + } +`; + +const CustomModal = styled(Modal)` + display: flex; + align-items: center; + justify-content: center; +`; + +const ModalContent = styled(Box)` + width: 400px; + background-color: ${(props) => props.theme.modalBackgroundColor}; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); + padding: 16px; + position: relative; +`; + +const ModalContent2 = styled(Box)` + width: 800px; + background-color: ${(props) => props.theme.modalBackgroundColor}; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); + padding: 16px; + position: relative; +`; + +const ListItem = styled(Box)` + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + border-bottom: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.3); + margin-bottom: 8px; + background-color: ${(props) => props.theme.modalBackgroundColor}; +`; + +const ParticipantCard = styled(Box)` + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); + margin-bottom: 16px; + background-color: ${(props) => props.theme.modalBackgroundColor}; +`; + +const ParticipantInfo = styled(Box)` + display: flex; + align-items: center; +`; + +const ParticipantActions = styled(Box)` + display: flex; + align-items: center; + gap: 8px; +`; + +const StatusLabel = styled(Typography)` + margin-right: 16px; + font-weight: bold; + color: ${(props) => props.theme.color}; +`; + +const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#FF6384']; + +// Main Dashboard Component +export function CampagneDashboard() { + const [openFeedback, setOpenFeedback] = useState(false); + const [openParticipants, setOpenParticipants] = useState(false); + const [alertOpen, setAlertOpen] = useState(false); + const [darkMode, setDarkMode] = useState(false); + + const handleOpenFeedback = () => setOpenFeedback(true); + const handleCloseFeedback = () => setOpenFeedback(false); + + const handleOpenParticipants = () => setOpenParticipants(true); + const handleCloseParticipants = () => setOpenParticipants(false); + + const handleAlertOpen = () => setAlertOpen(true); + const handleAlertClose = () => setAlertOpen(false); + + const [filter, setFilter] = useState('all'); + + const filteredParticipants = participantList.filter((participant) => { + if (filter === 'all') return true; + return participant.status === filter; + }); + + return ( + <DashboardContainer theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h4" gutterBottom> + Dashboard Hackathon + </Typography> + + <Box display="flex" alignItems="center" mb={2}> + <Typography variant="body1" mr={2}> + Mode Sombre + </Typography> + <Switch checked={darkMode} onChange={() => setDarkMode(!darkMode)} /> + </Box> + + {/* Existing graphs */} + {/* Distribution des Statuts des Participants */} + <GraphContainer> + <GraphTitle variant="h6">Distribution des Statuts des Participants</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <PieChart> + <Pie + data={participantStatusData} + dataKey="value" + nameKey="name" + cx="50%" + cy="50%" + outerRadius={100} + fill="#8884d8" + label + > + {participantStatusData.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> + ))} + </Pie> + <Tooltip /> + <Legend /> + </PieChart> + </ResponsiveContainer> + <StyledButton + variant="contained" + color="primary" + onClick={handleOpenParticipants} + > + Gérer + </StyledButton> + </GraphContainer> + + {/* New graphs */} + {/* Distribution des types d'utilisateurs */} + <GraphContainer> + <GraphTitle variant="h6">Distribution des utilisateurs</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <RadarChart outerRadius={90} data={userTypesData}> + <PolarGrid /> + <PolarAngleAxis dataKey="type" /> + <PolarRadiusAxis /> + <Radar + name="Types d'utilisateurs" + dataKey="value" + stroke="#8884d8" + fill="#8884d8" + fillOpacity={0.6} + /> + <Tooltip /> + <Legend /> + </RadarChart> + </ResponsiveContainer> + </GraphContainer> + + {/* Répartition des participants par activité */} + <GraphContainer> + <GraphTitle variant="h6">Répartition des participants par activité</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <PieChart> + <Pie + data={activityDistributionData} + dataKey="value" + nameKey="activity" + cx="50%" + cy="50%" + outerRadius={100} + fill="#82ca9d" + label + > + {activityDistributionData.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> + ))} + </Pie> + <Tooltip /> + <Legend /> + </PieChart> + </ResponsiveContainer> + </GraphContainer> + + {/* Participation aux sessions d’intervenants */} + <GraphContainer> + <GraphTitle variant="h6">Participation aux sessions d’intervenants</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <RadarChart outerRadius={90} data={sessionsParticipationData}> + <PolarGrid /> + <PolarAngleAxis dataKey="session" /> + <PolarRadiusAxis /> + <Radar + name="Sessions" + dataKey="participants" + stroke="#8884d8" + fill="#8884d8" + fillOpacity={0.6} + /> + <Tooltip /> + <Legend /> + </RadarChart> + </ResponsiveContainer> + </GraphContainer> + + {/* Feedback des utilisateurs */} + <GraphContainer> + <GraphTitle variant="h6">Feedback des utilisateurs</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <RadarChart outerRadius={90} data={userFeedbackData}> + <PolarGrid /> + <PolarAngleAxis dataKey="rating" /> + <PolarRadiusAxis /> + <Radar + name="Feedback" + dataKey="count" + stroke="#8884d8" + fill="#8884d8" + fillOpacity={0.6} + /> + <Tooltip /> + <Legend /> + </RadarChart> + </ResponsiveContainer> + <StyledButton + variant="contained" + color="primary" + onClick={handleOpenFeedback} + > + Voir Feedback + </StyledButton> + </GraphContainer> + + + + {/* Feedback Modal */} + <CustomModal open={openFeedback} onClose={handleCloseFeedback}> + <ModalContent theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h6" gutterBottom> + Feedback + </Typography> + {feedbackList.map((item, index) => ( + <ListItem key={index} theme={darkMode ? darkModeStyles : lightModeStyles}> + <Box> + <Typography variant="body2" color="textSecondary"> + {item.username} + </Typography> + <Typography variant="body1">{item.comment}</Typography> + </Box> + <Box> + {[...Array(item.rating)].map((_, i) => ( + <span key={i} style={{ color: '#FFD700' }}>★</span> + ))} + {[...Array(5 - item.rating)].map((_, i) => ( + <span key={i} style={{ color: '#555' }}>★</span> + ))} + </Box> + </ListItem> + ))} + </ModalContent> + </CustomModal> + + {/* Participant Management Modal */} + <CustomModal open={openParticipants} onClose={handleCloseParticipants}> + <ModalContent2 theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h5" gutterBottom> + Gestion des Participants + </Typography> + <Box mb={2} display="flex" justifyContent="space-around"> + <Button variant="outlined" onClick={() => setFilter('all')}> + Tous + </Button> + <Button variant="outlined" onClick={() => setFilter('participant')}> + Participants + </Button> + <Button variant="outlined" onClick={() => setFilter('demande')}> + Demandes + </Button> + </Box> + {filteredParticipants.map((participant, index) => ( + <ParticipantCard key={index} theme={darkMode ? darkModeStyles : lightModeStyles}> + <ParticipantInfo> + <Typography variant="body2" color="textSecondary"> + {participant.status === 'participant' ? '@participant' : participant.status === 'demande' ? '@demande d\'inscription' : participant.status === 'en attente' ? '@en attente' : '@rejete'} + </Typography> + <IconButton color="primary" onClick={() => alert('Redirecting to user page')}> + <PersonIcon /> + </IconButton> + <Typography variant="body1">{participant.username}</Typography> + </ParticipantInfo> + <ParticipantActions> + {participant.status === 'demande' ? ( + <> + <Button variant="contained" color="primary" onClick={() => alert('Accepted')}> + Accepter + </Button> + <Button variant="contained" color="error" onClick={handleAlertOpen}> + Refuser + </Button> + </> + ) : participant.status === 'participant' ? ( + <Button variant="contained" color="error" onClick={handleAlertOpen}> + Renvoyer + </Button> + ) : participant.status === 'en attente' ? ( + <Button variant="contained" color="primary" onClick={() => alert('Request is now pending')}> + Valider + </Button> + ) : ( + <Button variant="contained" color="primary" onClick={() => alert('Rejected')}> + Réexamen + </Button> + )} + </ParticipantActions> + </ParticipantCard> + ))} + </ModalContent2> + </CustomModal> + + {/* Refuse Alert Modal */} + <CustomModal open={alertOpen} onClose={handleAlertClose}> + <ModalContent theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h6" gutterBottom> + Confirmer le refus + </Typography> + <Typography variant="body1" gutterBottom> + Êtes-vous sûr de vouloir refuser cette demande? + </Typography> + <Box mt={2} display="flex" justifyContent="space-between"> + <Button variant="contained" color="primary" onClick={() => { setAlertOpen(false); alert('Demande refusée'); }}> + Oui + </Button> + <Button variant="contained" color="error" onClick={handleAlertClose}> + Non + </Button> + </Box> + </ModalContent> + </CustomModal> + </DashboardContainer> + ); +} diff --git a/src/shared/components/event/campagneDeroulement.tsx b/src/shared/components/event/campagneDeroulement.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5761f558388e9053df1ae820a69364a9f447f5c7 --- /dev/null +++ b/src/shared/components/event/campagneDeroulement.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Box, Typography, Card, Grid } from '@mui/material'; +import Image from 'next/image'; +import {DeroulementItem,deroulementItems} from 'src/shared/_mock/_event' + + +export function CampagneDeroulement() { + return ( + <Box sx={{ padding: 2 }}> + <Box + display="flex" + alignItems="center" + justifyContent="center" + sx={{ + height: 60, + mb: 4, + border: '2px solid grey', + borderRadius: 2, + bgcolor: 'background.default', + p: 2 + }} + > + <Typography variant="h2" sx={{ fontSize: '1.5rem', color: 'grey.130' }}>sessions</Typography> + </Box> + {deroulementItems.map(item => ( + <Card key={item.id} sx={{ mb: 2, boxShadow: 3, borderRadius: 2 }}> + <Grid container> + <Grid item xs={10} md={8} sx={{ p: 2 }}> + <Typography variant="h5" sx={{ fontSize: '1.5rem', mb: 2 }}>{item.title}</Typography> + <Typography variant="body1">{item.description}</Typography> + </Grid> + <Grid item xs={12} md={4} sx={{ position: 'relative', minHeight: 200, borderTopRightRadius: 8, borderBottomRightRadius: 8, overflow: 'hidden' }}> + <Image + src={item.image} + alt={item.title} + layout="fill" + objectFit="cover" + style={{ borderRadius: '0 8px 8px 0' }} + /> + </Grid> + </Grid> + </Card> + ))} + </Box> + ); +} diff --git a/src/shared/components/event/createCampaign.tsx b/src/shared/components/event/createCampaign.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2eb9bb29b3faee5a78565f93cffacb2fd56e6cc3 --- /dev/null +++ b/src/shared/components/event/createCampaign.tsx @@ -0,0 +1,142 @@ +import React, { useState } from 'react'; +import { TextField, Button, Box, Typography, FormControl, FormHelperText } from '@mui/material'; +import { Event } from 'src/shared/_mock/_event'; // Make sure this import is correct + +type CreateCampaignProps = { + onClose: () => void; + onCreate: (newEvent: Event) => void; +}; + +export function CreateCampaign({ onClose, onCreate }: CreateCampaignProps) { + const [name, setName] = useState(''); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [coverImage, setCoverImage] = useState(''); + const [shortDescription, setShortDescription] = useState(''); + const [duration, setDuration] = useState(''); + const [activities, setActivities] = useState(''); + + const [errors, setErrors] = useState({ + name: false, + startDate: false, + endDate: false, + coverImage: false, + shortDescription: false, + duration: false, + activities: false, + }); + + const handleCreate = () => { + const newErrors = { + name: !name, + startDate: !startDate, + endDate: !endDate, + coverImage: !coverImage, + shortDescription: !shortDescription, + duration: !duration, + activities: !activities, + }; + + setErrors(newErrors); + + if (Object.values(newErrors).some((error) => error)) { + return; // Stop execution if there are errors + } + + const newEvent: Event = { + id: Date.now(), // Use a unique ID, for example, timestamp + name, + startDate, + endDate, + coverImage, + shortDescription, + duration, + activities, + type: 'campagne', // Automatically set type to 'campagne' + visibility:'public' + }; + onCreate(newEvent); // Pass the new event to the parent component + onClose(); // Close the modal + }; + + return ( + <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> + <Typography variant="h6">Créer un Hackathon</Typography> + <FormControl error={errors.name} fullWidth> + <TextField + label="Nom" + variant="outlined" + value={name || `Titre du Campagne !!`} + onChange={(e) => setName(e.target.value)} + /> + {errors.name && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.startDate} fullWidth> + <TextField + label="Date de début" + type="date" + InputLabelProps={{ shrink: true }} + variant="outlined" + value={startDate } + onChange={(e) => setStartDate(e.target.value)} + /> + {errors.startDate && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.endDate} fullWidth> + <TextField + label="Date de fin" + type="date" + InputLabelProps={{ shrink: true }} + variant="outlined" + value={endDate } + onChange={(e) => setEndDate(e.target.value)} + /> + {errors.endDate && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + + + <FormControl error={errors.shortDescription} fullWidth> + <TextField + label="Description courte" + variant="outlined" + multiline + rows={3} + value={shortDescription || `Description..`} + onChange={(e) => setShortDescription(e.target.value)} + /> + {errors.shortDescription && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.duration} fullWidth> + <TextField + label="Durée" + variant="outlined" + value={duration || `Durée en jours..`} + onChange={(e) => setDuration(e.target.value)} + /> + {errors.duration && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.activities} fullWidth> + <TextField + label="Activités" + variant="outlined" + value={activities || `Activités.. !!`} + onChange={(e) => setActivities(e.target.value)} + /> + {errors.activities && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <Button + variant="contained" + color="primary" + onClick={handleCreate} + > + Créer + </Button> + </Box> + ); +} diff --git a/src/shared/components/event/createHackathon.tsx b/src/shared/components/event/createHackathon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..09116a72af72f6a07263b1fdfcba150180880ce0 --- /dev/null +++ b/src/shared/components/event/createHackathon.tsx @@ -0,0 +1,142 @@ +import React, { useState } from 'react'; +import { TextField, Button, Box, Typography, FormControl, FormHelperText } from '@mui/material'; +import { Event } from 'src/shared/_mock/_event'; // Make sure this import is correct + +type CreateHackathonProps = { + onClose: () => void; + onCreate: (newEvent: Event) => void; +}; + +export function CreateHackathon({ onClose, onCreate }: CreateHackathonProps) { + const [name, setName] = useState(''); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [coverImage, setCoverImage] = useState(''); + const [shortDescription, setShortDescription] = useState(''); + const [duration, setDuration] = useState(''); + const [activities, setActivities] = useState(''); + + const [errors, setErrors] = useState({ + name: false, + startDate: false, + endDate: false, + coverImage: false, + shortDescription: false, + duration: false, + activities: false, + }); + + const handleCreate = () => { + const newErrors = { + name: !name, + startDate: !startDate, + endDate: !endDate, + coverImage: !coverImage, + shortDescription: !shortDescription, + duration: !duration, + activities: !activities, + }; + + setErrors(newErrors); + + if (Object.values(newErrors).some((error) => error)) { + return; // Stop execution if there are errors + } + + const newEvent: Event = { + id: Date.now(), // Use a unique ID, for example, timestamp + name, + startDate, + endDate, + coverImage, + shortDescription, + duration, + activities, + type: 'hackathon', + visibility: 'public' + }; + onCreate(newEvent); // Pass the new event to the parent component + onClose(); // Close the modal + }; + + return ( + <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> + <Typography variant="h6">Créer un Hackathon</Typography> + + <FormControl error={errors.name} fullWidth> + <TextField + label="Nom" + variant="outlined" + value={name || `Titre du Hackathon !!`} + onChange={(e) => setName(e.target.value)} + /> + {errors.name && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.startDate} fullWidth> + <TextField + label="Date de début" + type="date" + InputLabelProps={{ shrink: true }} + variant="outlined" + value={startDate } + onChange={(e) => setStartDate(e.target.value)} + /> + {errors.startDate && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.endDate} fullWidth> + <TextField + label="Date de fin" + type="date" + InputLabelProps={{ shrink: true }} + variant="outlined" + value={endDate } + onChange={(e) => setEndDate(e.target.value)} + /> + {errors.endDate && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + + <FormControl error={errors.shortDescription} fullWidth> + <TextField + label="Description courte" + variant="outlined" + multiline + rows={3} + value={shortDescription || `Description..`} + onChange={(e) => setShortDescription(e.target.value)} + /> + {errors.shortDescription && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.duration} fullWidth> + <TextField + label="Durée" + variant="outlined" + value={duration || `une seule Journée`} + onChange={(e) => setDuration(e.target.value)} + /> + {errors.duration && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <FormControl error={errors.activities} fullWidth> + <TextField + label="Activités" + variant="outlined" + value={activities || `Activités.. !!`} + onChange={(e) => setActivities(e.target.value)} + /> + {errors.activities && <FormHelperText>Ce champ est requis</FormHelperText>} + </FormControl> + + <Button + variant="contained" + color="primary" + onClick={handleCreate} + > + Créer + </Button> + </Box> + ); +} diff --git a/src/shared/components/event/hackathonDashboard.tsx b/src/shared/components/event/hackathonDashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3dc2dcb1ffc56da4d6b4e3bb5e1b15be1d1c1a73 --- /dev/null +++ b/src/shared/components/event/hackathonDashboard.tsx @@ -0,0 +1,364 @@ +import React, { useState } from 'react'; +import { PieChart, Pie, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, Legend, Tooltip, ResponsiveContainer, Cell } from 'recharts'; +import { Box, Typography, Button, Modal, IconButton, Switch } from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import styled from 'styled-components'; + +import { feedbackList, hackathonFeedbackData, participantList, participantStatusData,projectProgressData,workshopParticipationData } from 'src/shared/_mock/_event' +// Light and dark mode styles +const lightModeStyles = { + backgroundColor: '#fff', + color: '#000', + buttonBackgroundColor: '#f0f0f0', + buttonHoverColor: '#e0e0e0', + modalBackgroundColor: '#fff', + modalBorderColor: '#ddd', +}; + +const darkModeStyles = { + backgroundColor: '#121212', + color: '#e0e0e0', + buttonBackgroundColor: '#1f1f1f', + buttonHoverColor: '#333333', + modalBackgroundColor: '#1e1e1e', + modalBorderColor: '#333', +}; + +// Styled components with props for dynamic styling +const DashboardContainer = styled(Box)` + padding: 16px; + background-color: ${(props) => props.theme.backgroundColor}; + color: ${(props) => props.theme.color}; +`; + +const GraphTitle = styled(Typography)` + margin-top: 16px; + color: ${(props) => props.theme.color}; +`; + +const GraphContainer = styled(Box)` + position: relative; + margin-top: 16px; +`; + +const StyledButton = styled(Button)` + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + background-color: ${(props) => props.theme.buttonBackgroundColor}; + color: ${(props) => props.theme.color}; + + &:hover { + background-color: ${(props) => props.theme.buttonHoverColor}; + } +`; + +const CustomModal = styled(Modal)` + display: flex; + align-items: center; + justify-content: center; +`; + +const ModalContent = styled(Box)` + width: 400px; + background-color: ${(props) => props.theme.modalBackgroundColor}; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); + padding: 16px; + position: relative; +`; + +const ModalContent2 = styled(Box)` + width: 800px; + background-color: ${(props) => props.theme.modalBackgroundColor}; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); + padding: 16px; + position: relative; +`; + +const ListItem = styled(Box)` + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + border-bottom: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.3); + margin-bottom: 8px; + background-color: ${(props) => props.theme.modalBackgroundColor}; +`; + +const ParticipantCard = styled(Box)` + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); + margin-bottom: 16px; + background-color: ${(props) => props.theme.modalBackgroundColor}; +`; + +const ParticipantInfo = styled(Box)` + display: flex; + align-items: center; +`; + +const ParticipantActions = styled(Box)` + display: flex; + align-items: center; + gap: 8px; +`; + +const StatusLabel = styled(Typography)` + margin-right: 16px; + font-weight: bold; + color: ${(props) => props.theme.color}; +`; + +const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#FF6384']; + +// Main Dashboard Component +export function HackathonDashboard() { + const [open, setOpen] = useState(false); + const [open2, setOpen2] = useState(false); + const [alertOpen, setAlertOpen] = useState(false); + const [darkMode, setDarkMode] = useState(false); + + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + const handleOpen2 = () => setOpen2(true); + const handleClose2 = () => setOpen2(false); + + const handleAlertOpen = () => setAlertOpen(true); + const handleAlertClose = () => setAlertOpen(false); + + const [filter, setFilter] = useState('all'); + + const filteredParticipants = participantList.filter((participant) => { + if (filter === 'all') return true; + return participant.status === filter; + }); + + return ( + <DashboardContainer theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h4" gutterBottom> + Dashboard Hackathon + </Typography> + + <Box display="flex" alignItems="center" mb={2}> + <Typography variant="body1" mr={2}> + Mode Sombre + </Typography> + <Switch checked={darkMode} onChange={() => setDarkMode(!darkMode)} /> + </Box> + + <GraphContainer> + <GraphTitle variant="h6">Distribution des Statuts des Participants</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <PieChart> + <Pie + data={participantStatusData} + dataKey="value" + nameKey="name" + cx="50%" + cy="50%" + outerRadius={100} + fill="#8884d8" + label + > + {participantStatusData.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> + ))} + </Pie> + <Tooltip /> + <Legend /> + </PieChart> + </ResponsiveContainer> + <StyledButton + variant="contained" + color="primary" + onClick={handleOpen2} + > + Gérer + </StyledButton> + </GraphContainer> + + <GraphContainer> + <GraphTitle variant="h6">Participation aux Ateliers</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <RadarChart outerRadius={90} data={workshopParticipationData}> + <PolarGrid /> + <PolarAngleAxis dataKey="subject" /> + <PolarRadiusAxis /> + <Radar + name="Ateliers" + dataKey="A" + stroke="#8884d8" + fill="#8884d8" + fillOpacity={0.6} + /> + <Tooltip /> + <Legend /> + </RadarChart> + </ResponsiveContainer> + </GraphContainer> + <GraphContainer> + <GraphTitle variant="h6">Avancement des projets de compétition</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <PieChart> + <Pie + data={projectProgressData} + dataKey="value" + nameKey="name" + cx="50%" + cy="50%" + outerRadius={100} + fill="#82ca9d" + label + > + {projectProgressData.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> + ))} + </Pie> + <Tooltip /> + <Legend /> + </PieChart> + </ResponsiveContainer> + </GraphContainer> + + <GraphContainer> + <GraphTitle variant="h6">Feedback au Hackathon</GraphTitle> + <ResponsiveContainer width="100%" height={300}> + <RadarChart outerRadius={90} data={hackathonFeedbackData}> + <PolarGrid /> + <PolarAngleAxis dataKey="subject" /> + <PolarRadiusAxis /> + <Radar + name="Feedback" + dataKey="A" + stroke="#8884d8" + fill="#8884d8" + fillOpacity={0.6} + /> + <Tooltip /> + <Legend /> + </RadarChart> + </ResponsiveContainer> + <StyledButton + variant="contained" + color="primary" + onClick={handleOpen} + > + Voir Feedback + </StyledButton> + </GraphContainer> + + {/* Feedback Modal */} + <CustomModal open={open} onClose={handleClose}> + <ModalContent theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h6" gutterBottom> + Feedback + </Typography> + {feedbackList.map((item, index) => ( + <ListItem key={index} theme={darkMode ? darkModeStyles : lightModeStyles}> + <Box> + <Typography variant="body2" color="textSecondary"> + {item.username} + </Typography> + <Typography variant="body1">{item.comment}</Typography> + </Box> + <Box> + {[...Array(item.rating)].map((_, i) => ( + <span key={i} style={{ color: '#FFD700' }}>★</span> + ))} + {[...Array(5 - item.rating)].map((_, i) => ( + <span key={i} style={{ color: '#555' }}>★</span> + ))} + </Box> + </ListItem> + ))} + </ModalContent> + </CustomModal> + + {/* Participant Management Modal */} + <CustomModal open={open2} onClose={handleClose2}> + <ModalContent2 theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h5" gutterBottom> + Gestion des Participants + </Typography> + <Box mb={2} display="flex" justifyContent="space-around"> + <Button variant="outlined" onClick={() => setFilter('all')}> + Tous + </Button> + <Button variant="outlined" onClick={() => setFilter('participant')}> + Participants + </Button> + <Button variant="outlined" onClick={() => setFilter('demande')}> + Demandes + </Button> + </Box> + {filteredParticipants.map((participant, index) => ( + <ParticipantCard key={index} theme={darkMode ? darkModeStyles : lightModeStyles}> + <ParticipantInfo> + <Typography variant="body2" color="textSecondary"> + {participant.status === 'participant' ? '@participant' : participant.status === 'demande' ? '@demande d\'inscription' : participant.status === 'en attente' ? '@en attente' : '@rejete'} + </Typography> + <IconButton color="primary" onClick={() => alert('Redirecting to user page')}> + <PersonIcon /> + </IconButton> + <Typography variant="body1">{participant.username}</Typography> + </ParticipantInfo> + <ParticipantActions> + {participant.status === 'demande' ? ( + <> + <Button variant="contained" color="primary" onClick={() => alert('Accepted')}> + Accepter + </Button> + <Button variant="contained" color="error" onClick={handleAlertOpen}> + Refuser + </Button> + </> + ) : participant.status === 'participant' ? ( + <Button variant="contained" color="error" onClick={handleAlertOpen}> + Renvoyer + </Button> + ) : participant.status === 'en attente' ? ( + <Button variant="contained" color="primary" onClick={() => alert('Request is now pending')}> + Valider + </Button> + ) : ( + <Button variant="contained" color="primary" onClick={() => alert('Rejected')}> + Réexamen + </Button> + )} + </ParticipantActions> + </ParticipantCard> + ))} + </ModalContent2> + </CustomModal> + + {/* Refuse Alert Modal */} + <CustomModal open={alertOpen} onClose={handleAlertClose}> + <ModalContent theme={darkMode ? darkModeStyles : lightModeStyles}> + <Typography variant="h6" gutterBottom> + Confirmer le refus + </Typography> + <Typography variant="body1" gutterBottom> + Êtes-vous sûr de vouloir refuser cette demande? + </Typography> + <Box mt={2} display="flex" justifyContent="space-between"> + <Button variant="contained" color="primary" onClick={() => { setAlertOpen(false); alert('Demande refusée'); }}> + Oui + </Button> + <Button variant="contained" color="error" onClick={handleAlertClose}> + Non + </Button> + </Box> + </ModalContent> + </CustomModal> + </DashboardContainer> + ); +} diff --git a/src/shared/components/event/hackathonDeroulement.tsx b/src/shared/components/event/hackathonDeroulement.tsx new file mode 100644 index 0000000000000000000000000000000000000000..44cd3816c0f80c2f8bb48eefb9a4aeff02eb47df --- /dev/null +++ b/src/shared/components/event/hackathonDeroulement.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Box, Typography, Card, Grid } from '@mui/material'; +import Image from 'next/image'; +import {DeroulementItem,deroulementItems} from 'src/shared/_mock/_event' + +export function HackathonDeroulement() { + return ( + <Box sx={{ padding: 2 }}> + <Box + display="flex" + alignItems="center" + justifyContent="center" + sx={{ + height: 60, + mb: 4, + border: '2px solid grey', + borderRadius: 2, + bgcolor: 'background.default', + p: 2 + }} + > + <Typography variant="h2" sx={{ fontSize: '1.5rem', color: 'grey.900' }}>Ateliers</Typography> + </Box> + {deroulementItems.map(item => ( + <Card key={item.id} sx={{ mb: 2, boxShadow: 3, borderRadius: 2 }}> + <Grid container> + <Grid item xs={10} md={8} sx={{ p: 2 }}> + <Typography variant="h5" sx={{ fontSize: '1.5rem', mb: 2 }}>{item.title}</Typography> + <Typography variant="body1">{item.description}</Typography> + </Grid> + <Grid item xs={12} md={4} sx={{ position: 'relative', minHeight: 200, borderTopRightRadius: 8, borderBottomRightRadius: 8, overflow: 'hidden' }}> + <Image + src={item.image} + alt={item.title} + layout="fill" + objectFit="cover" + style={{ borderRadius: '0 8px 8px 0' }} + /> + </Grid> + </Grid> + </Card> + ))} + </Box> + ); +} diff --git a/src/shared/components/event/roomAdministration.tsx b/src/shared/components/event/roomAdministration.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d05889ac741681d013ac64c778dc859bdde1dc68 --- /dev/null +++ b/src/shared/components/event/roomAdministration.tsx @@ -0,0 +1,300 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Typography, Button, IconButton, Menu, MenuItem, Switch, useTheme } from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import MicIcon from '@mui/icons-material/Mic'; +import CameraAltIcon from '@mui/icons-material/CameraAlt'; +import ScreenShareIcon from '@mui/icons-material/ScreenShare'; +import styled from 'styled-components'; + +// Styled components using theme-aware colors +const Container = styled(Box)` + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + background-color: ${({ theme }) => theme.palette.background.default}; +`; + +const FilterContainer = styled(Box)` + display: flex; + justify-content: center; + margin-bottom: 20px; +`; + +const PermissionContainer = styled(Box)` + width: 60%; + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 20px; +`; + +const ParticipantList = styled(Box)` + width: 60%; + display: flex; + flex-direction: column; + gap: 10px; +`; + +const ParticipantCard = styled(Box)` + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; + background-color: ${({ theme }) => theme.palette.background.paper}; + border-radius: 8px; + box-shadow: ${({ theme }) => theme.shadows[1]}; +`; + +const ProfileIcon = styled(PersonIcon)` + color: ${({ theme }) => theme.palette.primary.main}; +`; +// Define a type for the RoleTag's props +interface RoleTagProps { + isHost: boolean; +} + +// Styled components using theme-aware colors +const RoleTag = styled(Typography)<RoleTagProps>` + font-size: 12px; + color: ${({ isHost, theme }) => isHost ? theme.palette.primary.main : theme.palette.text.secondary}; +`; + +// Define a type for the Label's props +interface LabelProps { + enabled: boolean; +} +const Username = styled(Typography)` + font-weight: bold; + font-size: 14px; +`; + +const KickoutButton = styled(Button)` + color: ${({ theme }) => theme.palette.error.main}; +`; + +const PermissionSwitch = styled(Box)` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +`; + +const Label = styled(Typography)<LabelProps>` + font-size: 14px; + color: ${({ enabled, theme }) => enabled ? theme.palette.success.main : theme.palette.error.main}; +`; + +interface Participant { + id: number; + username: string; + role: string; +} + +type FilterType = 'all' | 'participant' | 'host'; + +type PermissionKey = 'mic' | 'cam' | 'shareScreen'; + +const AdministrationRoom = () => { + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + const [permissionsAnchorEl, setPermissionsAnchorEl] = useState(null); + const [currentFilter, setCurrentFilter] = useState<FilterType>('all'); + const [filteredParticipants, setFilteredParticipants] = useState<Participant[]>([]); + const [filterPermissions, setFilterPermissions] = useState({ + participant: { mic: false, cam: false, shareScreen: false }, + host: { mic: false, cam: false, shareScreen: false }, + }); + const [currentPermissions, setCurrentPermissions] = useState({ mic: false, cam: false, shareScreen: false }); + + + useEffect(() => { + const participants =[ + { id: 1, username: 'User 1', role: 'participant' }, + { id: 2, username: 'User 2', role: 'host' }, + { id: 3, username: 'User 3', role: 'participant' }, + ]; // fetch or calculate participants here + + // Filter participants based on the current filter + const newFilteredParticipants = participants.filter((participant) => { + if (currentFilter === 'all') return true; + return participant.role === currentFilter; + }); + + setFilteredParticipants(newFilteredParticipants); + + if (currentFilter !== 'all') { + setCurrentPermissions(filterPermissions[currentFilter as 'participant' | 'host']); + } + }, [currentFilter, filterPermissions]); + + const handleFilterChange = (newFilter: FilterType) => { + setCurrentFilter(newFilter); + if (newFilter === 'all') { + setCurrentPermissions({ mic: false, cam: false, shareScreen: false }); + } else { + setCurrentPermissions(filterPermissions[newFilter]); + } + }; + + const handleMenuClick = (_event:any) => { + setAnchorEl(_event.currentTarget); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handlePermissionsClick = (_event:any) => { + setPermissionsAnchorEl(_event.currentTarget); + }; + + const handlePermissionsClose = () => { + setPermissionsAnchorEl(null); + }; + + const handleTogglePermission = (permission: PermissionKey) => () => { + const updatedPermissions = { ...currentPermissions, [permission]: !currentPermissions[permission] }; + setCurrentPermissions(updatedPermissions); + if (currentFilter !== 'all') { + setFilterPermissions({ + ...filterPermissions, + [currentFilter]: updatedPermissions, + }); + } + }; + + return ( + <Container theme={theme}> + <FilterContainer> + <Button variant="outlined" onClick={() => handleFilterChange('all')}> + All + </Button> + <Button variant="outlined" onClick={() => handleFilterChange('participant')}> + Participant + </Button> + <Button variant="outlined" onClick={() => handleFilterChange('host')}> + Host + </Button> + </FilterContainer> + + {currentFilter !== 'all' && ( + + <PermissionContainer> + <PermissionSwitch > + <Box display="flex" alignItems="center"> + <MicIcon style={{ marginRight: '8px' }} /> + <Label enabled={currentPermissions.mic} theme={theme}>{currentPermissions.mic ? 'Enabled' : 'Disabled'}</Label> + </Box> + <Switch + checked={currentPermissions.mic} + onChange={handleTogglePermission('mic')} + color="primary" + /> + </PermissionSwitch> + <PermissionSwitch> + <Box display="flex" alignItems="center"> + <CameraAltIcon style={{ marginRight: '8px' }} /> + <Label enabled={currentPermissions.cam} theme={theme}>{currentPermissions.cam ? 'Enabled' : 'Disabled'}</Label> + </Box> + <Switch + checked={currentPermissions.cam} + onChange={handleTogglePermission('cam')} + color="primary" + /> + </PermissionSwitch> + <PermissionSwitch> + <Box display="flex" alignItems="center"> + <ScreenShareIcon style={{ marginRight: '8px' }} /> + <Label enabled={currentPermissions.shareScreen} theme={theme}> + {currentPermissions.shareScreen ? 'Enabled' : 'Disabled'} + </Label> + </Box> + <Switch + checked={currentPermissions.shareScreen} + onChange={handleTogglePermission('shareScreen')} + color="primary" + /> + </PermissionSwitch> + </PermissionContainer> + )} + + <ParticipantList> + {filteredParticipants.map((participant) => ( + <ParticipantCard key={participant.id} theme={theme}> + <Box display="flex" alignItems="center" gap={2}> + <ProfileIcon theme={theme} /> + <Box> + <Username>{participant.username}</Username> + <RoleTag isHost={participant.role === 'host'} theme={theme}>@{participant.role}</RoleTag> + </Box> + </Box> + <Box> + <IconButton onClick={handleMenuClick}> + <MoreVertIcon /> + </IconButton> + <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}> + <MenuItem onClick={handlePermissionsClick} style={{ color: theme.palette.primary.main }}> + Permissions + </MenuItem> + <MenuItem onClick={handleMenuClose} style={{ color: theme.palette.error.main }}> + Kickout + </MenuItem> + </Menu> + <Menu + anchorEl={permissionsAnchorEl} + open={Boolean(permissionsAnchorEl)} + onClose={handlePermissionsClose} + > + <MenuItem> + <PermissionSwitch> + <Box display="flex" alignItems="center"> + <MicIcon style={{ marginRight: '8px' }} /> + <Label enabled={currentPermissions.mic} theme={theme}>{currentPermissions.mic ? 'Enabled' : 'Disabled'}</Label> + </Box> + <Switch + checked={currentPermissions.mic} + onChange={handleTogglePermission('mic')} + color="primary" + /> + </PermissionSwitch> + </MenuItem> + <MenuItem> + <PermissionSwitch> + <Box display="flex" alignItems="center"> + <CameraAltIcon style={{ marginRight: '8px' }} /> + <Label enabled={currentPermissions.cam} theme={theme}>{currentPermissions.cam ? 'Enabled' : 'Disabled'}</Label> + </Box> + <Switch + checked={currentPermissions.cam} + onChange={handleTogglePermission('cam')} + color="primary" + /> + </PermissionSwitch> + </MenuItem> + <MenuItem> + <PermissionSwitch> + <Box display="flex" alignItems="center"> + <ScreenShareIcon style={{ marginRight: '8px' }} /> + <Label enabled={currentPermissions.shareScreen} theme={theme}> + {currentPermissions.shareScreen ? 'Enabled' : 'Disabled'} + </Label> + </Box> + <Switch + checked={currentPermissions.shareScreen} + onChange={handleTogglePermission('shareScreen')} + color="primary" + /> + </PermissionSwitch> + </MenuItem> + </Menu> + </Box> + </ParticipantCard> + ))} + </ParticipantList> + </Container> + ); +}; + +export default AdministrationRoom; diff --git a/src/shared/components/event/teamSpace.tsx b/src/shared/components/event/teamSpace.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3e702fda4a8a768d2e415338416a51a9650b7ed2 --- /dev/null +++ b/src/shared/components/event/teamSpace.tsx @@ -0,0 +1,235 @@ +import React, { useState } from 'react'; +import { + Box, + Button, + Card, + CardContent, + Typography, + Modal, + IconButton, + Avatar, + TextField, + Grid, + List, + ListItem, + ListItemText, + Divider, + Tooltip, + MenuItem, + Select, + FormControl, + InputLabel, + Paper, +} from '@mui/material'; +import { + Add as AddIcon, + Edit as EditIcon, + Delete as DeleteIcon, + ExpandMore as ExpandMoreIcon, + EmojiEmotions as EmojiEmotionsIcon, + MoreVert as MoreVertIcon, + Report as ReportIcon, + RemoveCircle as RemoveCircleIcon, +} from '@mui/icons-material'; +import { dummyGuide, dummyMessages, dummySubDiscussions, dummyTests, Emoji, emojiList, Message, ShowModalType, SubDiscussion } from 'src/shared/_mock/_event'; + +type Member = string; + + +const TeamSpace: React.FC = () => { + const [messages, setMessages] = useState<Message[]>(dummyMessages); + const [subDiscussions, setSubDiscussions] = useState<SubDiscussion[]>(dummySubDiscussions); + const [showModal, setShowModal] = useState<ShowModalType>(false); + const [newSubDiscussionTitle, setNewSubDiscussionTitle] = useState<string>(''); + const [editingSubDiscussion, setEditingSubDiscussion] = useState<SubDiscussion | null>(null); + const [selectedReaction, setSelectedReaction] = useState<number | null>(null); + const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false); + + // Handle reactions for messages + const handleAddReaction = (msgId: number, reaction: Emoji) => { + const newMessages = messages.map((msg) => { + if (msg.id === msgId) { + if (!msg.reactions[reaction]) { + msg.reactions[reaction] = 0; + } + msg.reactions[reaction] += 1; + } + return msg; + }); + setMessages(newMessages); + }; + + // Open emoji picker for a message + const handleOpenEmojiPicker = (msgId: number) => { + setSelectedReaction(msgId); + setShowEmojiPicker(true); + }; + + // Add a new reaction from the emoji picker + const handleSelectEmoji = (emoji: Emoji) => { + if (selectedReaction !== null) { + handleAddReaction(selectedReaction, emoji); + setShowEmojiPicker(false); + setSelectedReaction(null); + } + }; + + // Adding or editing sub-discussion + const handleAddOrEditSubDiscussion = () => { + if (editingSubDiscussion) { + setSubDiscussions(subDiscussions.map(sub => + sub.id === editingSubDiscussion.id ? { ...sub, title: newSubDiscussionTitle } : sub + )); + } else { + const newSubDiscussion: SubDiscussion = { id: Date.now(), title: newSubDiscussionTitle, members: [] }; + setSubDiscussions([...subDiscussions, newSubDiscussion]); + } + setNewSubDiscussionTitle(''); + setShowModal(false); + setEditingSubDiscussion(null); + }; + + // Handle opening modal for editing sub-discussion + const handleEditSubDiscussion = (sub: SubDiscussion) => { + setEditingSubDiscussion(sub); + setNewSubDiscussionTitle(sub.title); + setShowModal(true); + }; + + // Handle adding/removing members + const handleMemberChange = (subId: number, member: Member) => { + setSubDiscussions(subDiscussions.map(sub => + sub.id === subId ? { ...sub, members: sub.members.includes(member) ? sub.members.filter(m => m !== member) : [...sub.members, member] } : sub + )); + }; + return ( + <Box sx={{ padding: 2 }}> + <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}> + <Typography variant="h6">Chat Principal</Typography> + <Button variant="contained" color="primary" onClick={() => setShowModal(true)}> + <AddIcon /> Nouvelle Sous-Discussion + </Button> + </Box> + + {/* Sub-discussion headers */} + <Box sx={{ display: 'flex', gap: 2, marginBottom: 2 }}> + {subDiscussions.map(sub => ( + <Paper key={sub.id} sx={{ padding: 1, display: 'flex', alignItems: 'center' }}> + <Typography variant="body1">{sub.title}</Typography> + <IconButton size="small" sx={{ marginLeft: 'auto' }} onClick={() => handleEditSubDiscussion(sub)}> + <EditIcon /> + </IconButton> + <IconButton size="small"> + <DeleteIcon /> + </IconButton> + </Paper> + ))} + </Box> + + {/* Messages Section */} + <Box sx={{ maxHeight: '400px', overflowY: 'auto', border: '1px solid lightgrey', padding: 2, marginBottom: 2 }}> + {messages.map(msg => ( + <Box key={msg.id} sx={{ display: 'flex', alignItems: 'center', marginBottom: 1 }}> + <Avatar src={msg.profileImage} sx={{ marginRight: 2 }} /> + <Box sx={{ flexGrow: 1 }}> + <Typography variant="body2"><strong>{msg.username}</strong>: {msg.text}</Typography> + <Box sx={{ display: 'flex', gap: 1, marginTop: 0.5 }}> + {Object.keys(msg.reactions).map((emoji) => ( + <Typography key={emoji} variant="body2"> + {emoji} {msg.reactions[emoji] > 1 && msg.reactions[emoji]} + </Typography> + ))} + <Tooltip title="Ajouter une réaction"> + <IconButton size="small" onClick={() => handleOpenEmojiPicker(msg.id)}> + <EmojiEmotionsIcon /> + </IconButton> + </Tooltip> + </Box> + </Box> + <IconButton size="small" sx={{ marginLeft: 'auto' }}> + <MoreVertIcon /> + </IconButton> + </Box> + ))} + </Box> + + {/* "Projet Affecté" Button */} + <Button variant="contained" color="secondary" onClick={() => setShowModal('project')}> + Projet Affecté + </Button> + + {/* Modal for adding/editing sub-discussion */} + <Modal open={showModal === true} onClose={() => setShowModal(false)} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '400px', bgcolor: 'background.paper', padding: 3 }}> + <Typography variant="h6">{editingSubDiscussion ? 'Modifier' : 'Ajouter'} une Sous-Discussion</Typography> + <TextField + fullWidth + label="Titre de la Sous-Discussion" + value={newSubDiscussionTitle} + onChange={(e) => setNewSubDiscussionTitle(e.target.value)} + sx={{ marginTop: 2 }} + /> +<Typography variant="body2" sx={{ marginTop: 2 }}>Membres:</Typography> +{['user1', 'user2', 'user3'].map(member => ( + <Button + key={member} + variant={editingSubDiscussion && editingSubDiscussion.members.includes(member) ? 'contained' : 'outlined'} + onClick={() => { + if (editingSubDiscussion) { + handleMemberChange(editingSubDiscussion.id, member); // Now it's guaranteed to be a number + } + }} + sx={{ margin: 0.5 }} + > + {member} + </Button> +))} +<Button variant="contained" color="primary" sx={{ marginTop: 2 }} onClick={handleAddOrEditSubDiscussion}> + {editingSubDiscussion ? 'Modifier' : 'Ajouter'} +</Button> + + </Box> + </Modal> + + {/* Modal for Project Assignment */} + <Modal open={showModal === 'project'} onClose={() => setShowModal(false)} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '600px', bgcolor: 'background.paper', padding: 3 }}> + <Typography variant="h6">Guide du Projet</Typography> + <TextField + fullWidth + label="Guide" + multiline + rows={4} + value={dummyGuide} + sx={{ marginTop: 2, marginBottom: 2 }} + /> + <Typography variant="h6">Tests</Typography> + {dummyTests.map((test, index) => ( + <Box key={index} sx={{ display: 'flex', alignItems: 'center', marginBottom: 1 }}> + <Typography variant="body2">{test.title}</Typography> + <Button variant="contained" color="primary" sx={{ marginLeft: 'auto' }}> + Consulter + </Button> + </Box> + ))} + </Box> + </Modal> + + {/* Emoji Picker Modal */} + <Modal open={showEmojiPicker} onClose={() => setShowEmojiPicker(false)} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '300px', bgcolor: 'background.paper', padding: 2 }}> + <Typography variant="h6">Sélectionnez un Emoji</Typography> + <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, marginTop: 2 }}> + {emojiList.map((emoji, index) => ( + <Button key={index} onClick={() => handleSelectEmoji(emoji)}> + {emoji} + </Button> + ))} + </Box> + </Box> + </Modal> + </Box> + ); +}; + +export default TeamSpace; diff --git a/src/shared/layouts/config-nav-dashboard.tsx b/src/shared/layouts/config-nav-dashboard.tsx index 5ac4e2e49c2ffc3e7bf986be8985ecbf713dbb7c..2d8ff9ff7ee551a260f7aa1955b799742457a95f 100644 --- a/src/shared/layouts/config-nav-dashboard.tsx +++ b/src/shared/layouts/config-nav-dashboard.tsx @@ -16,9 +16,12 @@ const ICONS = { dashboard: icon('ic-dashboard'), parameter: icon('ic-parameter'), blank: icon('ic-blank'), + campagne: icon('ic-banking'), + hackathon: icon('ic-tour'), job: icon('ic-job'), user: icon('ic-user'), stats: icon('ic-analytics'), + event: icon('ic-tour'), }; // ---------------------------------------------------------------------- @@ -89,7 +92,36 @@ export const navData = [ path: paths.dashboard.stats.root, icon: ICONS.stats, }, - + + { + title: `Evénements`, + path: paths.dashboard.events.root, + icon: ICONS.event, + }, + ], + }, + /** + * Gestion des Evénements + */ + { + subheader: 'Gestion des Evénements', + items: [ + { + title: 'Hackathons', + path: paths.dashboard.hackathons, + icon: ICONS.hackathon, + }, + { + title: 'Campagnes', + path: paths.dashboard.campagnes, + icon: ICONS.campagne, + }, + { + title: 'Salons Audio-visuelles', + path: paths.dashboard.rooms, + icon: ICONS.campagne, + }, + ], }, /** @@ -110,4 +142,6 @@ export const navData = [ }, ], }, + + ]; diff --git a/src/shared/sections/events/campagnes/campagneList.tsx b/src/shared/sections/events/campagnes/campagneList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..512b94fa19c64c2370cd80c658bbbf743d7cc23b --- /dev/null +++ b/src/shared/sections/events/campagnes/campagneList.tsx @@ -0,0 +1,264 @@ +"use client"; + +import React, { useState } from 'react'; +import { styled } from '@mui/material/styles'; +import Grid from '@mui/material/Grid'; +import { Box, ThemeProvider, Button, Modal, TextField, Typography } from '@mui/material'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import CardActions from '@mui/material/CardActions'; +import Collapse from '@mui/material/Collapse'; +import IconButton, { IconButtonProps } from '@mui/material/IconButton'; +import ShareIcon from '@mui/icons-material/Share'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; + +import { eventsList,Event } from 'src/shared/_mock/_event'; +import { CampagneDeroulement } from 'src/shared/components/event/campagneDeroulement'; +import { CreateCampaign } from 'src/shared/components/event/createCampaign'; + +interface ExpandMoreProps extends IconButtonProps { + expand: boolean; +} + +const ExpandMore = styled((props: ExpandMoreProps) => { + const { expand, ...other } = props; + return <IconButton {...other} />; +})(({ theme, expand }) => ({ + transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + borderRadius: '50%', + height: 30, + width: 30, + backgroundColor: 'primary.main', + color: 'white', + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)', + '&:hover': { + backgroundColor: 'primary.dark', + }, + '& svg': { + fontSize: '2rem', + }, +})); + +type Props = { + title?: string; +}; + +const _events: Event[] = eventsList.filter((event) => event.visibility === 'public'); + +export function CampagneList({ title = 'Blank' }: Props) { + const [expandedId, setExpandedId] = useState<number | null>(null); + const [searchQuery, setSearchQuery] = useState(''); + const [eventType, setEventType] = useState(''); + const [modalOpen, setModalOpen] = useState(false); + const [deroulementModalOpen, setDeroulementModalOpen] = useState<number | null>(null); + const [events, setEvents] = useState<Event[]>(_events.filter(event => event.type === 'campagne')); + + const handleExpandClick = (id: number) => { + setExpandedId(expandedId === id ? null : id); + }; + + const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setSearchQuery(event.target.value); + }; + + const handleEventTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setEventType(event.target.value); + }; + + const handleOpenModal = () => { + setModalOpen(true); + }; + + const handleCloseModal = () => { + setModalOpen(false); + }; + + const handleCreatecampagne = (newEvent: Event) => { + setEvents([...events, newEvent]); + }; + + const handleOpenDeroulementModal = (id: number) => { + setDeroulementModalOpen(id); + }; + + const handleCloseDeroulementModal = () => { + setDeroulementModalOpen(null); + }; + + const filteredEvents = events.filter(event => { + const matchesSearchQuery = event.name.toLowerCase().includes(searchQuery.toLowerCase()) || + event.shortDescription.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesEventType = eventType === '' || + (eventType === 'hackathon' && event.type === 'hackathon') || + (eventType === 'recruitment' && event.type === 'campagne'); + return matchesSearchQuery && matchesEventType; + }); + + return ( + <DashboardContent maxWidth="xl"> + <Typography variant="h4" gutterBottom> {title} </Typography> + <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}> + <TextField + label="Recherche" + variant="outlined" + value={searchQuery} + onChange={handleSearchChange} + sx={{ marginRight: 3 }} + /> + <Button + variant="contained" + color="primary" + size="small" + sx={{ marginLeft: '8px' }} + onClick={handleOpenModal} + > + Ajouter une campagne + </Button> + </Box> + <Grid container spacing={3}> + {filteredEvents.map((event) => ( + <Grid item xs={12} sm={6} md={4} key={event.id}> + <Card sx={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}> + <ThemeProvider + theme={{ + palette: { + primary: { + main: 'grey.200', + dark: 'black', + }, + }, + }} + > + <Box + sx={{ + height: 10, + display: 'flex', + borderRadius: 1, + bgcolor: 'primary.main', + '&:hover': { + bgcolor: 'primary.dark', + }, + }} + /> + </ThemeProvider> + <CardContent> + <Typography variant="h6" align="center" fontWeight="bold"> + {event.name} + </Typography> + <Typography variant="subtitle2" align="center" color="text.secondary"> + {event.startDate} - {event.endDate} + </Typography> + + </CardContent> + <CardActions disableSpacing sx={{ justifyContent: 'space-between', padding: '8px' }}> + <Button + variant="contained" + color="primary" + size="small" + sx={{ flexGrow: 1, marginRight: '8px' }} + href={`/dashboard/events//Administration/campagnes/${event.id}`} + > + Gérer + </Button> + <Box sx={{ display: 'flex', alignItems: 'center' }}> + <IconButton aria-label="share" size="small"> + <ShareIcon /> + </IconButton> + <ExpandMore + expand={expandedId === event.id} + onClick={() => handleExpandClick(event.id)} + aria-expanded={expandedId === event.id} + aria-label="show more" + size="small" + > + <ExpandMoreIcon /> + </ExpandMore> + </Box> + </CardActions> + <Collapse in={expandedId === event.id} timeout="auto" unmountOnExit> + <CardContent sx={{ bgcolor: '#f9f9f9', borderRadius: 1, boxShadow: 2, mt: 2 }}> + <Typography variant="body2" color="text.secondary" paragraph> + {event.shortDescription} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Durée:</strong> {event.duration} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Activités:</strong> {event.activities} + </Typography> + <Button + variant="outlined" + color="secondary" + size="small" + onClick={() => handleOpenDeroulementModal(event.id)} + sx={{ mt: 2 }} + > + Intervenants + </Button> + </CardContent> + </Collapse> + </Card> + </Grid> + ))} + </Grid> + <Modal + open={modalOpen} + onClose={handleCloseModal} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + <CreateCampaign + onClose={handleCloseModal} + onCreate={handleCreatecampagne} + /> + </Box> + </Modal> + <Modal + open={deroulementModalOpen !== null} + onClose={handleCloseDeroulementModal} + aria-labelledby="modal-deroulement-title" + aria-describedby="modal-deroulement-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + // maxWidth: 800, + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + <CampagneDeroulement /> + </Box> + </Modal> + </DashboardContent> + ); +} diff --git a/src/shared/sections/events/campagnes/view/view.tsx b/src/shared/sections/events/campagnes/view/view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..00c149dde9e82060c2c66b5d6368b36ae5416a87 --- /dev/null +++ b/src/shared/sections/events/campagnes/view/view.tsx @@ -0,0 +1,817 @@ +'use client'; + +import React, { useState } from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import { styled } from '@mui/material/styles'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import { Event, eventsList, Session,Speaker } from 'src/shared/_mock/_event'; +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import Modal from '@mui/material/Modal'; +import TextField from '@mui/material/TextField'; +import { ArrowForwardIos, ArrowBackIos, LinkedIn } from '@mui/icons-material'; +import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; +import { CampagneDashboard } from 'src/shared/components/event/campagneDashboard'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Switch from '@mui/material/Switch'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { CardActions, Collapse, Divider, Grid, List, ListItem, ListItemText } from '@mui/material'; +import { HackathonDeroulement } from 'src/shared/components/event/hackathonDeroulement'; // Ensure this import is correct +import IconButton, { IconButtonProps } from '@mui/material/IconButton'; +import ShareIcon from '@mui/icons-material/Share'; + +interface ExpandMoreProps extends IconButtonProps { + expand: boolean; +} + +const ExpandMore = styled((props: ExpandMoreProps) => { + const { expand, ...other } = props; + return <IconButton {...other} />; +})(({ theme, expand }) => ({ + transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + borderRadius: '50%', + height: 30, + width: 30, + backgroundColor: 'primary.main', + color: 'white', + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)', + '&:hover': { + backgroundColor: 'primary.dark', + }, + '& svg': { + fontSize: '2rem', + }, +})); + +// Styled Components for Archiver Button and Switch Card +const ArchiverButton = styled(Button)(({ theme }) => ({ + position: 'fixed', + bottom: theme.spacing(2), + left: '50%', + transform: 'translateX(-50%)', + backgroundColor: 'grey', + color: 'white', + boxShadow: theme.shadows[4], +})); + +const SwitchCard = styled(Card)(({ theme }) => ({ + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + padding: theme.spacing(1), + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, + marginTop: theme.spacing(2), + backgroundColor: theme.palette.background.paper, +})); + +const HackathonModalContent = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.primary, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '600px', + maxWidth: '100%', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + textAlign: 'center', + [theme.breakpoints.down('sm')]: { + width: '90%', + }, +})); + +const ArchiveModalContent = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '400px', + maxWidth: '90%', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + textAlign: 'center', +})); + +// Styled other Components +const Div = styled('div')(({ theme }) => ({ + ...theme.typography.button, + backgroundColor: theme.palette.background.paper, + padding: theme.spacing(1), +})); + +const NavigationButton = styled(Button)(({ theme }) => ({ + position: 'absolute', + top: '50%', + transform: 'translateY(-50%)', + zIndex: 1, +})); + +const AnnouncementsPanel = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[1], + overflowY: 'auto', + maxHeight: 200, + position: 'relative', +})); + +const SpeakerCard = styled(Box)(({ theme }) => ({ + width: '100%', + height: 200, + backgroundSize: 'cover', + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-end', + textAlign: 'center', + margin: 0, + backgroundPosition: 'center', + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[2], +})); + +const AddActivityModal = styled(Modal)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const ModalContent = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '400px', + maxWidth: '90%', +})); + +const AccordionContainer = styled(Box)(({ theme }) => ({ + maxHeight: '400px', + overflowY: 'auto', + marginTop: theme.spacing(3), +})); + +const FloatingButton = styled(Button)(({ theme }) => ({ + position: 'absolute', + top: theme.spacing(2), + right: theme.spacing(2), + backgroundColor: 'white', + color: 'black', + borderRadius: '50%', + boxShadow: theme.shadows[4], + width: '36px', + height: '36px', + minWidth: 'unset', + fontSize: '24px', + padding: 0, + '&:hover': { + backgroundColor: 'white', + boxShadow: theme.shadows[6], + }, +})); + +const ScrollableBox = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + maxHeight: '80vh', + overflowY: 'auto', + width: '100%', + color: theme.palette.text.primary, + +})); + + + +const DashboardModal = styled(Modal)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const AnnouncementModal = styled(Modal)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const ModalPlaceholder = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '400px', + maxWidth: '90%', +})); + +type Props = { + eventId: { id: number }; +}; + +export function CampagneHome({ eventId }: Props) { + + const event: Event = eventsList.find((e) => e.id === eventId.id) || eventsList[0]; + const speakers = event?.speakers || []; + + + const [currentPage, setCurrentPage] = useState(0); + const itemsPerPage = 5; + const totalPages = Math.ceil(speakers.length / itemsPerPage); + + const [expandedId, setExpandedId] = useState<number | null>(null); + + const [deroulementModalOpen, setDeroulementModalOpen] = useState<number | null>(null); + const handleExpandClick = (id: number) => { + setExpandedId(expandedId === id ? null : id); + }; + const handleOpenDeroulementModal = (id: number) => { + setDeroulementModalOpen(id); + }; + + + const handleCloseDeroulementModal = () => { + setDeroulementModalOpen(null); + }; + const handlePrev = () => { + setCurrentPage((prev) => Math.max(prev - 1, 0)); +}; + +const handleNext = () => { + setCurrentPage((prev) => Math.min(prev + 1, totalPages - 1)); +}; + +const visibleSpeakers = speakers.slice(currentPage * itemsPerPage, (currentPage + 1) * itemsPerPage); + + + + const [announcementsExpanded, setAnnouncementsExpanded] = useState(true); + const [modalOpen, setModalOpen] = useState(false); + const [dashboardModalOpen, setDashboardModalOpen] = useState(false); + const [isSession, setIsSession] = useState(false); + const [selectedDateTime, setSelectedDateTime] = useState<Dayjs | null>(null); + const [activityTitle, setActivityTitle] = useState(''); + const [activityDescription, setActivityDescription] = useState(''); + const [announcementModalOpen, setAnnouncementModalOpen] = useState(false); + const [announcementTitle, setAnnouncementTitle] = useState(''); + const [announcementContent, setAnnouncementContent] = useState(''); + const [archiveModalOpen, setArchiveModalOpen] = useState(false); + const [isPublic, setIsPublic] = useState(true); + const [isActive, setIsActive] = useState(true); + + + const toggleAnnouncements = () => setAnnouncementsExpanded(!announcementsExpanded); + + const handleSessionClick = (session: Session) => { + const now = new Date(); + const sessionTime = new Date(session.time); + if (now >= sessionTime) { + window.location.href = session.roomLink; + } + }; + + const handleOpenModal = () => { + setModalOpen(true); + }; + + const handleCloseModal = () => { + setModalOpen(false); + setIsSession(false); + setSelectedDateTime(null); + setActivityTitle(''); + setActivityDescription(''); + }; + + const handleOpenDashboardModal = () => { + setDashboardModalOpen(true); + }; + + const handleCloseDashboardModal = () => { + setDashboardModalOpen(false); + }; + + const handleOpenAnnouncementModal = () => { + setAnnouncementModalOpen(true); + }; + + const handleCloseAnnouncementModal = () => { + setAnnouncementModalOpen(false); + setAnnouncementTitle(''); + setAnnouncementContent(''); + }; + + const handleDateTimeChange = (newValue: any) => { + setSelectedDateTime(newValue); + }; + + const handleFormSubmit = () => { + if (activityTitle && selectedDateTime) { + const selectedSpeakerId = isSession ? parseInt((document.getElementById('speakerSelect') as HTMLSelectElement).value, 10) : null; + const selectedSpeaker: Speaker | null = isSession && selectedSpeakerId + ? speakers.find((speaker) => speaker.id === selectedSpeakerId) || null + : null; + const newSession: Session = { + id: Date.now(), + title: activityTitle, + description: activityDescription, + time: selectedDateTime.toISOString(), + speaker: selectedSpeaker, + roomLink: '', + }; + + if (event?.sessions) { + event.sessions.push(newSession); + } else { + event.sessions = [newSession]; + } + handleCloseModal(); + } +}; + + const handleAnnouncementSubmit = () => { + console.log('Announcement added:', { title: announcementTitle, content: announcementContent }); + handleCloseAnnouncementModal(); + }; + + const handleArchiveModalOpen = () => setArchiveModalOpen(true); + const handleArchiveModalClose = () => setArchiveModalOpen(false); + + const handleArchiveConfirm = () => { + // Logic to archive the event + console.log('Event archived'); + setArchiveModalOpen(false); + }; + + const handleSwitchChange = (_event_: React.ChangeEvent<HTMLInputElement>) => { + setIsPublic(_event_.target.checked); + }; + const handleSwitchChange2 = (event_: React.ChangeEvent<HTMLInputElement>) => { + setIsActive(event_.target.checked); + }; + + const toggleHackathon = (hackathonId: number) => { + setSelectedHackathons((prevSelected) => + prevSelected.includes(hackathonId) + ? prevSelected.filter(id => id !== hackathonId) + : [...prevSelected, hackathonId] + ); + }; + + + + + const handleHackathonSelect = (hackathon: Event) => { + setSelectedHackathons((prevSelected) => + prevSelected.includes(hackathon.id) + ? prevSelected.filter(id => id !== hackathon.id) + : [...prevSelected, hackathon.id] + ); + }; + + const handleValidation = () => { + // Filter out already selected hackathons that are not currently selected + const updatedHackathons = campagnHackathons.filter( + (hackathon) => selectedHackathons.includes(hackathon.id) + ); + + // Add newly selected hackathons that are not in the current list + const newHackathons = hackathons.filter( + (hackathon) => selectedHackathons.includes(hackathon.id) && !campagnHackathons.some(existing => existing.id === hackathon.id) + ); + + setCampagnHackathons([...updatedHackathons, ...newHackathons]); + handleCloseHackathonModal(); + }; + + + const handleCreateNewHackathon = () => { + console.log('Créer un nouveau Hackathon'); + handleCloseHackathonModal(); + }; + + + const [hackathonModalOpen, setHackathonModalOpen] = useState(false); + const [selectedHackathons, setSelectedHackathons] = useState<number[]>([]); + const [campagnHackathons, setCampagnHackathons] = useState<Event[]>([]); + + const handleOpenHackathonModal = () => { + setHackathonModalOpen(true); + }; + + const handleCloseHackathonModal = () => { + setHackathonModalOpen(false); + }; + + + + const hackathons = eventsList.filter((e) => e.type === 'hackathon'); + + + + return ( + <DashboardContent maxWidth="xl"> + + <Box display="flex" justifyContent="space-between" alignItems="center"> + <Div>{event?.name}</Div> + {/* Archiver Button */} + <Button onClick={handleArchiveModalOpen} color="error" > + Archiver + </Button> + <SwitchCard> + <FormControlLabel + control={<Switch checked={isPublic} onChange={handleSwitchChange} />} + label={isPublic ? "Publique" : "Privée"} + sx={{ color: isPublic ? 'green' : 'blue' }} + /> + </SwitchCard> + </Box> + <Box display="flex" justifyContent="space-between" alignItems="center" mt={3}> + <Button onClick={handleOpenDashboardModal}>Statistiques</Button> + <Button onClick={handleOpenModal}>Ajouter une activité</Button> + <Button onClick={handleOpenHackathonModal}>Intégrer un Hackathon</Button> + <FormControlLabel + control={<Switch checked={isActive} onChange={handleSwitchChange2} />} + label={isActive ? "Forum" : "Forum"} + sx={{ color: isActive ? 'green' : 'grey' }} + /> + </Box> + + {isActive && ( + <Card sx={{ backgroundColor: 'grey', padding: 2, mt: 3 }}> + <Typography variant="h6">Forum De Discussion</Typography> + <Box display="flex" justifyContent="flex-end" mt={2}> + <Button variant="contained" color="primary"> + Participer au Salon + </Button> + </Box> + </Card> + )} + {announcementsExpanded && ( + <AnnouncementsPanel> + <Typography variant="h6">Annonces</Typography> + <Typography variant="body2">Voici quelques annonces importantes...</Typography> + <FloatingButton onClick={handleOpenAnnouncementModal}>+</FloatingButton> + </AnnouncementsPanel> + )} + + <Box mt={5}> + <Typography variant="h6">Chronologie</Typography> + <AccordionContainer> + {event?.sessions?.map((session) => ( + <Accordion key={session.id}> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Box sx={{ display: 'flex', width: '100%' }}> + <Typography sx={{ flex: 1 }}> + {new Date(session.time).toLocaleDateString()} | {new Date(session.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} + </Typography> + <Typography sx={{ flex: 2, textAlign: 'center' }}> + {session.title} {session.speaker ? "| ".concat(session.speaker.name) : null} + </Typography> + </Box> + </AccordionSummary> + <AccordionDetails> + <Typography>{session.description}</Typography> + {session.speaker && ( + <Button onClick={() => handleSessionClick(session)} startIcon={<ArrowForwardIos />}> + Participer + </Button> + )} + </AccordionDetails> + </Accordion> + ))} + </AccordionContainer> + </Box> + + <Box mt={5}> + <Typography variant="h6">Intervenants</Typography> + <Box sx={{ display: 'flex', overflow: 'hidden', position: 'relative' }}> + {currentPage > 0 && ( + <NavigationButton onClick={handlePrev} sx={{ left: '-30px' }}> + <ArrowBackIos /> + </NavigationButton> + )} + + <Box sx={{ display: 'flex', justifyContent: 'space-between', overflow: 'hidden', width: '100%' }}> + {visibleSpeakers.map((speaker) => ( + <SpeakerCard + key={speaker.id} + sx={{ backgroundImage: `url(${speaker.imageUrl})`, backgroundSize: 'cover' }} + > + <Box + sx={{ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + padding: '5px', + }} + > + <Box sx={{ textAlign: 'left', color: 'white' }}> + <Typography variant="subtitle1">{speaker.name}</Typography> + <Typography variant="caption">{speaker.role}</Typography> + </Box> + <Button + href='speaker.linkedinUrl' + target="_blank" + > + <LinkedIn /> + </Button> + </Box> + </SpeakerCard> + ))} + </Box> + + {currentPage < totalPages - 1 && ( + <NavigationButton onClick={handleNext} sx={{ right: '-30px' }}> + <ArrowForwardIos /> + </NavigationButton> + )} + </Box> + </Box> + + {/* Dashboard Modal */} + <DashboardModal open={dashboardModalOpen} onClose={handleCloseDashboardModal}> + <ScrollableBox> + <CampagneDashboard /> + </ScrollableBox> + </DashboardModal> + + {/* Add Activity Modal */} + {/* Modal for adding activity */} + <AddActivityModal open={modalOpen} onClose={handleCloseModal}> + <ModalContent> + <Typography variant="h6">Ajouter une activité</Typography> + <DateTimePicker + label="Date & Heure" + value={selectedDateTime} + onChange={handleDateTimeChange} + /> + <TextField + label="Titre de l'activité" + fullWidth + margin="normal" + value={activityTitle} + onChange={(e) => setActivityTitle(e.target.value)} + /> + <TextField + label="Description de l'activité" + fullWidth + margin="normal" + multiline + rows={4} + value={activityDescription} + onChange={(e) => setActivityDescription(e.target.value)} + /> + <FormControlLabel + control={ + <Switch + checked={isSession} + onChange={(e) => setIsSession(e.target.checked)} + /> + } + label="Est-ce une session?" + /> + {isSession && ( + <TextField + id="speakerSelect" + label="Intervenants" + fullWidth + margin="normal" + select + SelectProps={{ + native: true, + }} + > + {event?.speakers?.map((speaker: Speaker) => ( + <option key={speaker.id} value={speaker.id}> + {speaker.name} + </option> + ))} + </TextField> +)} + + <Box mt={2} textAlign="right"> + <Button onClick={handleCloseModal} sx={{ marginRight: 2 }}>Annuler</Button> + <Button onClick={handleFormSubmit} variant="contained" sx={{ backgroundColor: '#333', color: '#fff' }}>Ajouter</Button> + </Box> + </ModalContent> + </AddActivityModal> + + {/* Announcement Modal */} + <AnnouncementModal open={announcementModalOpen} onClose={handleCloseAnnouncementModal}> + <ModalPlaceholder> + <Typography variant="h6" component="h2" gutterBottom> + Nouvelle annonce + </Typography> + + <TextField + label="Titre" + value={announcementTitle} + onChange={(e) => setAnnouncementTitle(e.target.value)} + fullWidth + margin="normal" + /> + + <TextField + label="Contenu" + value={announcementContent} + onChange={(e) => setAnnouncementContent(e.target.value)} + fullWidth + margin="normal" + multiline + rows={4} + /> + + <Box mt={3} display="flex" justifyContent="flex-end"> + <Button onClick={handleAnnouncementSubmit} variant="contained" color="primary"> + Ajouter + </Button> + </Box> + </ModalPlaceholder> + </AnnouncementModal> + + + + {/* Archive Confirmation Modal */} + <Modal open={archiveModalOpen} onClose={handleArchiveModalClose}> + <ArchiveModalContent> + <Typography variant="h6" gutterBottom> + {`Voulez-vous terminer l'événement et le déplacer à l'archivage?`} + </Typography> + <Box mt={3} display="flex" justifyContent="space-around"> + <Button onClick={handleArchiveModalClose} variant="outlined" color="secondary"> + Non + </Button> + <Button onClick={handleArchiveConfirm} variant="contained" color="error"> + Oui + </Button> + </Box> + </ArchiveModalContent> + </Modal> + + + {/* Hackathon Modal */} + <Modal open={hackathonModalOpen} onClose={handleCloseHackathonModal}> + <HackathonModalContent> + <Typography variant="h6" gutterBottom> + Intégrer un Hackathon + </Typography> + <ScrollableBox> + <List> + {hackathons.map((hackathon) => ( + <div key={hackathon.id}> + <ListItem> + <ListItemText primary={hackathon.name} secondary={hackathon.shortDescription} /> + <Button + variant={selectedHackathons.includes(hackathon.id) ? 'contained' : 'outlined'} + onClick={() => handleHackathonSelect(hackathon)} + sx={{ minWidth: '100px' }} + > + {selectedHackathons.includes(hackathon.id) ? 'Sélectionné' : 'Sélectionner'} + </Button> + </ListItem> + <Divider /> + </div> + ))} + </List> + </ScrollableBox> + <Box mt={2}> + <Button variant="contained" color="secondary" onClick={handleCreateNewHackathon}> + Créer un nouveau Hackathon + </Button> + </Box> + <Box mt={2}> + <Button variant="contained" color="primary" onClick={handleValidation}> + Valider + </Button> + </Box> + + </HackathonModalContent> +</Modal> + + <Box mt={5}> + <Typography variant="h6">{campagnHackathons.length === 0 ? + <Div >Hackathons Intégrés : Néant...</Div>:"Hackathons Intégrés"}</Typography> + <Grid container spacing={3}> + { + campagnHackathons.map((__event) => ( + <Grid item xs={12} sm={6} md={4} key={__event.id}> + <Card sx={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}> + + <Box + sx={{ + height: 10, + display: 'flex', + borderRadius: 1, + bgcolor: 'primary.main', + '&:hover': { + bgcolor: 'primary.dark', + }, + }} + /> + <CardContent> + <Typography variant="h6" align="center" fontWeight="bold"> + {__event.name} + </Typography> + <Typography variant="subtitle2" align="center" color="text.secondary"> + {__event.startDate} - {__event.endDate} + </Typography> + + </CardContent> + <CardActions disableSpacing sx={{ justifyContent: 'space-between', padding: '8px' }}> + <Button + variant="contained" + color="primary" + size="small" + sx={{ flexGrow: 1, marginRight: '8px' }} + href={`/dashboard/events/Administration/hackathons/${__event.id}`} + > + Gérer + </Button> + <Box sx={{ display: 'flex', alignItems: 'center' }}> + <IconButton aria-label="share" size="small"> + <ShareIcon /> + </IconButton> + <ExpandMore + expand={expandedId === __event.id} + onClick={() => handleExpandClick(__event.id)} + aria-expanded={expandedId === __event.id} + aria-label="show more" + size="small" + > + <ExpandMoreIcon /> + </ExpandMore> + </Box> + </CardActions> + <Collapse in={expandedId === __event.id} timeout="auto" unmountOnExit> + <CardContent sx={{ bgcolor: '#f9f9f9', borderRadius: 1, boxShadow: 2, mt: 2 }}> + <Typography variant="body2" color="text.secondary" paragraph> + {event.shortDescription} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Durée:</strong> {__event.duration} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Activités:</strong> {__event.activities} + </Typography> + <Button + variant="outlined" + color="secondary" + size="small" + onClick={() => handleOpenDeroulementModal(__event.id)} + sx={{ mt: 2 }} + > + Intervenants + </Button> + </CardContent> + </Collapse> + </Card> + </Grid> + ))} + </Grid> + + </Box> + + + <Modal + open={deroulementModalOpen !== null} + onClose={handleCloseDeroulementModal} + aria-labelledby="modal-deroulement-title" + aria-describedby="modal-deroulement-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + <HackathonDeroulement /> + </Box> + </Modal> + + </DashboardContent> + ); +} diff --git a/src/shared/sections/events/eventList.tsx b/src/shared/sections/events/eventList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..82bcf5f6f1bd61f716f3f932e8ce170bcd2a6d12 --- /dev/null +++ b/src/shared/sections/events/eventList.tsx @@ -0,0 +1,219 @@ +'use client'; + +import React, { useState } from 'react'; +import { styled } from '@mui/material/styles'; +import Grid from '@mui/material/Grid'; +import { Box, ThemeProvider, Button, Modal, TextField, Typography } from '@mui/material'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import CardActions from '@mui/material/CardActions'; +import Collapse from '@mui/material/Collapse'; +import IconButton, { IconButtonProps } from '@mui/material/IconButton'; +import ShareIcon from '@mui/icons-material/Share'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import MenuItem from '@mui/material/MenuItem'; + +import { eventsList,Event } from 'src/shared/_mock/_event'; // Make sure this import is correct +import { HackathonDeroulement } from 'src/shared/components/event/hackathonDeroulement'; +import { CampagneDeroulement } from 'src/shared/components/event/campagneDeroulement'; + + +interface ExpandMoreProps extends IconButtonProps { + expand: boolean; +} + +const ExpandMore = styled((props: ExpandMoreProps) => { + const { expand, ...other } = props; + return <IconButton {...other} />; +})(({ theme, expand }) => ({ + transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)', + marginLeft: 'auto', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), +})); + +type Props = { + title?: string; +}; + +export function EventList({ title = 'Blank' }: Props) { + const [expandedId, setExpandedId] = useState<number | null>(null); + const [searchQuery, setSearchQuery] = useState(''); + const [eventType, setEventType] = useState(''); + const [selectedEventType, setSelectedEventType] = useState<'hackathon' | 'campagne' | null>(null); + + const handleExpandClick = (id: number) => { + setExpandedId(expandedId === id ? null : id); + }; + + const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setSearchQuery(event.target.value); + }; + + const handleEventTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setEventType(event.target.value); + }; + + const events: Event[] = eventsList.filter((event: Event) => event.visibility === 'public'); + + const filteredEvents = events.filter(event => { + const matchesSearchQuery = event.name.toLowerCase().includes(searchQuery.toLowerCase()) || + event.shortDescription.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesEventType = eventType === '' || + (eventType === 'hackathon' && event.type === 'hackathon') || + (eventType === 'recruitment' && event.type === 'campagne'); + return matchesSearchQuery && matchesEventType; + }); + + const [deroulementModalOpen, setDeroulementModalOpen] = useState<number | null>(null); + + const handleOpenDeroulementModal = (_eventId: number, _eventType: 'hackathon' | 'campagne') => { + setDeroulementModalOpen(_eventId); + setSelectedEventType(_eventType); + }; + + const handleCloseDeroulementModal = () => { + setDeroulementModalOpen(null); + setSelectedEventType(null); + }; + + return ( + <DashboardContent maxWidth="xl"> + <Typography variant="h4" gutterBottom> {title} </Typography> + <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}> + <TextField + label="Recherche" + variant="outlined" + value={searchQuery} + onChange={handleSearchChange} + sx={{ marginRight: 3 }} + /> + <TextField + select + label="Type d'événement" + value={eventType} + onChange={handleEventTypeChange} + variant="outlined" + sx={{ width: 200 }} + > + <MenuItem value="">Tous</MenuItem> + <MenuItem value="hackathon">Hackathon</MenuItem> + <MenuItem value="recruitment">Campagne de Recrutement</MenuItem> + </TextField> + </Box> + <Grid container spacing={3}> + {filteredEvents.map((event) => ( + <Grid item xs={12} sm={6} md={4} key={event.id}> + <Card sx={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}> + <ThemeProvider + theme={{ + palette: { + primary: { + main: 'grey.200', + dark: 'black', + }, + }, + }} + > + <Box + sx={{ + height: 10, + display: 'flex', + borderRadius: 1, + bgcolor: 'primary.main', + '&:hover': { + bgcolor: 'primary.dark', + }, + }} + /> + </ThemeProvider> + <CardContent> + <Typography variant="h6" align="center" fontWeight="bold"> + {event.name} + </Typography> + <Typography variant="subtitle2" align="center" color="text.secondary"> + {event.startDate} - {event.endDate} + </Typography> + </CardContent> + <CardActions disableSpacing sx={{ justifyContent: 'space-between', padding: '8px' }}> + <Button + variant="contained" + color="primary" + size="small" + sx={{ flexGrow: 1, marginRight: '8px' }} + href={`/dashboard/events/${event.type === 'hackathon' ? 'hackathons' : 'campagnes'}/${event.id}`} + > + Participer + </Button> + <Box sx={{ display: 'flex', alignItems: 'center' }}> + <IconButton aria-label="share" size="small"> + <ShareIcon /> + </IconButton> + <ExpandMore + expand={expandedId === event.id} + onClick={() => handleExpandClick(event.id)} + aria-expanded={expandedId === event.id} + aria-label="show more" + size="small" + > + <ExpandMoreIcon /> + </ExpandMore> + </Box> + </CardActions> + <Collapse in={expandedId === event.id} timeout="auto" unmountOnExit> + <CardContent sx={{ bgcolor: '#f9f9f9', borderRadius: 1, boxShadow: 2, mt: 2 }}> + <Typography variant="body2" color="text.secondary" paragraph> + {event.shortDescription} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Durée:</strong> {event.duration} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Activités:</strong> {event.activities} + </Typography> + <Button + variant="outlined" + color="secondary" + size="small" + onClick={() => handleOpenDeroulementModal(event.id, event.type)} + sx={{ mt: 2 }} + > + Intervenants + </Button> + </CardContent> + </Collapse> + </Card> + </Grid> + ))} + </Grid> + <Modal + open={deroulementModalOpen !== null} + onClose={handleCloseDeroulementModal} + aria-labelledby="modal-deroulement-title" + aria-describedby="modal-deroulement-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + {selectedEventType === 'hackathon' && <HackathonDeroulement />} + {selectedEventType === 'campagne' && <CampagneDeroulement />} + </Box> + </Modal> + </DashboardContent> + ); +} diff --git a/src/shared/sections/events/hackathons/hackathonList.tsx b/src/shared/sections/events/hackathons/hackathonList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c90cf26ad1e13f7968b748edd4fe808b1f522960 --- /dev/null +++ b/src/shared/sections/events/hackathons/hackathonList.tsx @@ -0,0 +1,264 @@ +"use client"; + +import React, { useState } from 'react'; +import { styled } from '@mui/material/styles'; +import Grid from '@mui/material/Grid'; +import { Box, ThemeProvider, Button, Modal, TextField, Typography } from '@mui/material'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import CardActions from '@mui/material/CardActions'; +import Collapse from '@mui/material/Collapse'; +import IconButton, { IconButtonProps } from '@mui/material/IconButton'; +import ShareIcon from '@mui/icons-material/Share'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import { CreateHackathon } from 'src/shared/components/event/createHackathon'; +import { HackathonDeroulement } from 'src/shared/components/event/hackathonDeroulement'; // Ensure this import is correct + +import { eventsList,Event } from 'src/shared/_mock/_event'; + +interface ExpandMoreProps extends IconButtonProps { + expand: boolean; +} + +const ExpandMore = styled((props: ExpandMoreProps) => { + const { expand, ...other } = props; + return <IconButton {...other} />; +})(({ theme, expand }) => ({ + transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + borderRadius: '50%', + height: 30, + width: 30, + backgroundColor: 'primary.main', + color: 'white', + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)', + '&:hover': { + backgroundColor: 'primary.dark', + }, + '& svg': { + fontSize: '2rem', + }, +})); + +type Props = { + title?: string; +}; + +const _events: Event[] = eventsList.filter((event) => event.visibility === 'public'); + +export function HackathonList({ title = 'Blank' }: Props) { + const [expandedId, setExpandedId] = useState<number | null>(null); + const [searchQuery, setSearchQuery] = useState(''); + const [eventType, setEventType] = useState(''); + const [modalOpen, setModalOpen] = useState(false); + const [deroulementModalOpen, setDeroulementModalOpen] = useState<number | null>(null); + const [events, setEvents] = useState<Event[]>(_events.filter(event => event.type === 'hackathon')); + + const handleExpandClick = (id: number) => { + setExpandedId(expandedId === id ? null : id); + }; + + const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setSearchQuery(event.target.value); + }; + + const handleEventTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setEventType(event.target.value); + }; + + const handleOpenModal = () => { + setModalOpen(true); + }; + + const handleCloseModal = () => { + setModalOpen(false); + }; + + const handleCreateHackathon = (newEvent: Event) => { + setEvents([...events, newEvent]); + }; + + const handleOpenDeroulementModal = (id: number) => { + setDeroulementModalOpen(id); + }; + + const handleCloseDeroulementModal = () => { + setDeroulementModalOpen(null); + }; + + const filteredEvents = events.filter(event => { + const matchesSearchQuery = event.name.toLowerCase().includes(searchQuery.toLowerCase()) || + event.shortDescription.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesEventType = eventType === '' || + (eventType === 'hackathon' && event.type === 'hackathon') || + (eventType === 'recruitment' && event.type === 'campagne'); + return matchesSearchQuery && matchesEventType; + }); + + return ( + <DashboardContent maxWidth="xl"> + <Typography variant="h4" gutterBottom> {title} </Typography> + <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}> + <TextField + label="Recherche" + variant="outlined" + value={searchQuery} + onChange={handleSearchChange} + sx={{ marginRight: 3 }} + /> + <Button + variant="contained" + color="primary" + size="small" + sx={{ marginLeft: '8px' }} + onClick={handleOpenModal} + > + Ajouter un Hackathon + </Button> + </Box> + <Grid container spacing={3}> + {filteredEvents.map((event) => ( + <Grid item xs={12} sm={6} md={4} key={event.id}> + <Card sx={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}> + <ThemeProvider + theme={{ + palette: { + primary: { + main: 'grey.200', + dark: 'black', + }, + }, + }} + > + <Box + sx={{ + height: 10, + display: 'flex', + borderRadius: 1, + bgcolor: 'primary.main', + '&:hover': { + bgcolor: 'primary.dark', + }, + }} + /> + </ThemeProvider> + <CardContent> + <Typography variant="h6" align="center" fontWeight="bold"> + {event.name} + </Typography> + <Typography variant="subtitle2" align="center" color="text.secondary"> + {event.startDate} - {event.endDate} + </Typography> + + </CardContent> + <CardActions disableSpacing sx={{ justifyContent: 'space-between', padding: '8px' }}> + <Button + variant="contained" + color="primary" + size="small" + sx={{ flexGrow: 1, marginRight: '8px' }} + href={`/dashboard/events/Administration/hackathons/${event.id}`} + > + Gérer + </Button> + <Box sx={{ display: 'flex', alignItems: 'center' }}> + <IconButton aria-label="share" size="small"> + <ShareIcon /> + </IconButton> + <ExpandMore + expand={expandedId === event.id} + onClick={() => handleExpandClick(event.id)} + aria-expanded={expandedId === event.id} + aria-label="show more" + size="small" + > + <ExpandMoreIcon /> + </ExpandMore> + </Box> + </CardActions> + <Collapse in={expandedId === event.id} timeout="auto" unmountOnExit> + <CardContent sx={{ bgcolor: '#f9f9f9', borderRadius: 1, boxShadow: 2, mt: 2 }}> + <Typography variant="body2" color="text.secondary" paragraph> + {event.shortDescription} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Durée:</strong> {event.duration} + </Typography> + <Typography variant="body2" color="text.secondary"> + <strong>Activités:</strong> {event.activities} + </Typography> + <Button + variant="outlined" + color="secondary" + size="small" + onClick={() => handleOpenDeroulementModal(event.id)} + sx={{ mt: 2 }} + > + Intervenants + </Button> + </CardContent> + </Collapse> + </Card> + </Grid> + ))} + </Grid> + <Modal + open={modalOpen} + onClose={handleCloseModal} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + <CreateHackathon + onClose={handleCloseModal} + onCreate={handleCreateHackathon} + /> + </Box> + </Modal> + <Modal + open={deroulementModalOpen !== null} + onClose={handleCloseDeroulementModal} + aria-labelledby="modal-deroulement-title" + aria-describedby="modal-deroulement-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + // maxWidth: 800, + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + <HackathonDeroulement /> + </Box> + </Modal> + </DashboardContent> + ); +} diff --git a/src/shared/sections/events/hackathons/view/competition.tsx b/src/shared/sections/events/hackathons/view/competition.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ee85ed14ec29645389fa8d42705e20ae28708625 --- /dev/null +++ b/src/shared/sections/events/hackathons/view/competition.tsx @@ -0,0 +1,346 @@ +'use client'; + +import React, { useState, ChangeEvent, MouseEvent } from 'react'; +import { + Box, + Button, + Card, + CardContent, + Typography, + Modal, + IconButton, + Avatar, + TextField, + Grid, + Paper, + List, + ListItem, + ListItemText, + Divider, + MenuItem, + Select, + FormControl, + InputLabel, + SelectChangeEvent +} from '@mui/material'; +import { + Add as AddIcon, + Edit as EditIcon, + Flag as FlagIcon, + MoreVert as MoreVertIcon +} from '@mui/icons-material'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import TeamSpace from 'src/shared/components/event/teamSpace'; + +import {Member, Team,teamsData,Test, testsData} from 'src/shared/_mock/_event' + +const CompetitionPage = () => { + const [showModal, setShowModal] = useState<string>(''); + const [teams, setTeams] = useState<Team[]>(teamsData); + const [selectedTests, setSelectedTests] = useState<Test[]>([]); + const [guidesAdded, setGuidesAdded] = useState<{ [key: string]: boolean }>({}); + const [newTeam, setNewTeam] = useState<Team>({ name: '', flag: '', members: [], guide: null, tests: [] }); + const [availableMembers, setAvailableMembers] = useState<Member[]>(teamsData[0].members); // Example available members + const [selectedMembers, setSelectedMembers] = useState<Member[]>([]); + const [currentTeam, setCurrentTeam] = useState<Team | null>(null); + const [selectedTeam, setSelectedTeam] = useState<string>(''); // State for selected team + + // Handle team selection from dropdown + const handleTeamChange = (event: SelectChangeEvent<string>) => { + setSelectedTeam(event.target.value as string); + }; + + const handleOpenModal = (modal: string, team: Team | null = null) => (event: MouseEvent) => { + setShowModal(modal); + if (team) setCurrentTeam(team); + }; + + const handleCloseModal = () => setShowModal(''); + + const handleAddTest = (test: Test) => { + if (currentTeam) { + setTeams(teams.map(team => + team.name === currentTeam.name + ? { ...team, tests: [...team.tests, test] } + : team + )); + setSelectedTests(prevTests => [...prevTests, test]); + } + }; + + const handleAddGuide = () => { + if (currentTeam) { + setTeams(teams.map(team => + team.name === currentTeam.name + ? { ...team, guide: 'Certain guide' } // Adjust as needed for guide data + : team + )); + setGuidesAdded(prev => ({ ...prev, [currentTeam.name]: true })); + } + // Do not close the modal window for assigning projects + }; + + const handleAddMember = (member: Member) => { + if (selectedMembers.length < 5) { + setSelectedMembers([...selectedMembers, member]); + } + }; + + const handleRemoveMember = (member: Member) => { + setSelectedMembers(selectedMembers.filter(m => m !== member)); + }; + + const handleCreateTeam = () => { + setTeams([...teams, { ...newTeam, members: selectedMembers, guide: null, tests: [] }]); + setNewTeam({ name: '', flag: '', members: [], guide: null, tests: [] }); + setSelectedMembers([]); + handleCloseModal(); + }; + + const handleAffectProjectsConfirm = () => { + if (Object.keys(guidesAdded).length === teams.length && selectedTests.length > 0) { + alert('Tous les guides et tests ont été ajoutés avec succès !'); + handleCloseModal(); + } else { + alert('Erreur : Tous les guides ou tests n\'ont pas été ajoutés.'); + } + }; + + const handleAffectProjectsCancel = () => { + handleCloseModal(); + }; + + return ( + <DashboardContent> + <Box sx={{ padding: 2, textAlign: 'center' }}> + {/* Main Interface */} + <Typography variant="h4" sx={{ marginBottom: 3 }}> + Compétition + </Typography> + <Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, marginBottom: 3 }}> + <Button variant="contained" color="primary" onClick={handleOpenModal('generateTeams')}> + Générer des Équipes + </Button> + <Button variant="contained" color="secondary" onClick={handleOpenModal('addTeam')}> + Ajouter une Équipe + </Button> + <Button variant="contained" color="success" onClick={handleOpenModal('validateTeams')}> + Valider les Équipes + </Button> + </Box> + + <Typography variant="h5" sx={{ marginBottom: 2 }}> + Équipes + </Typography> + <Grid container spacing={2} justifyContent="center"> + {teams.map((team, index) => ( + <Grid item xs={12} md={6} lg={4} key={index}> + <Card sx={{ boxShadow: 3, '&:hover': { boxShadow: 6 } }}> + <CardContent> + <Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 1 }}> + <Avatar src={team.flag} sx={{ width: 30, height: 30, marginRight: 1 }} /> + <Typography variant="h6">{team.name}</Typography> + <IconButton sx={{ marginLeft: 'auto' }} onClick={handleOpenModal('teamSpace', team)}> + <FlagIcon /> + </IconButton> + </Box> + <Divider sx={{ marginBottom: 1 }} /> + {team.members.map((member, idx) => ( + <Box key={idx} sx={{ display: 'flex', alignItems: 'center', marginBottom: 1 }}> + <Avatar src={member.profileImage} sx={{ width: 24, height: 24, marginRight: 1 }} /> + <Typography variant="body2">{member.username}</Typography> + </Box> + ))} + <Button variant="outlined" color="primary" onClick={handleOpenModal('personalizeTeam', team)}> + Personnaliser + </Button> + </CardContent> + </Card> + </Grid> + ))} + </Grid> + + <Button variant="contained" color="info" onClick={handleOpenModal('affectProjects')} sx={{ marginTop: 3 }}> + Affecter des Projets + </Button> + + {/* Modal Generate Teams */} + <Modal open={showModal === 'generateTeams'} onClose={handleCloseModal} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '80%', bgcolor: 'background.paper', padding: 3, maxHeight: '80vh', overflowY: 'auto' }}> + <Typography variant="h6" sx={{ marginBottom: 2 }}>Équipes Générées</Typography> + <Grid container spacing={2}> + {teams.map((team, index) => ( + <Grid item xs={12} key={index}> + <Paper sx={{ padding: 2 }}> + <Box sx={{ display: 'flex', alignItems: 'center', marginBottom: 1 }}> + <Avatar src={team.flag} sx={{ width: 30, height: 30, marginRight: 1 }} /> + <Typography variant="h6">{team.name}</Typography> + </Box> + <Divider sx={{ marginBottom: 1 }} /> + {team.members.map((member, idx) => ( + <Box key={idx} sx={{ display: 'flex', alignItems: 'center', marginBottom: 1 }}> + <Avatar src={member.profileImage} sx={{ width: 24, height: 24, marginRight: 1 }} /> + <Typography variant="body2">{member.username}</Typography> + </Box> + ))} + </Paper> + </Grid> + ))} + </Grid> + <Box sx={{ display: 'flex', justifyContent: 'flex-end', marginTop: 3 }}> + <Button variant="contained" color="primary" onClick={handleCloseModal}> + Fermer + </Button> + </Box> + </Box> + </Modal> + + {/* Modal Add Team */} + <Modal open={showModal === 'addTeam'} onClose={handleCloseModal} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '80%', bgcolor: 'background.paper', padding: 3 }}> + <Typography variant="h6" sx={{ marginBottom: 2 }}>Ajouter une Équipe</Typography> + <TextField label="Nom de l'Équipe" fullWidth sx={{ marginBottom: 2 }} value={newTeam.name} onChange={(e) => setNewTeam({ ...newTeam, name: e.target.value })} /> + <TextField label="URL du Drapeau" fullWidth sx={{ marginBottom: 2 }} value={newTeam.flag} onChange={(e) => setNewTeam({ ...newTeam, flag: e.target.value })} /> + <Typography variant="subtitle1">Membres</Typography> + <List dense> + {availableMembers.map((member, index) => ( + <ListItem key={index}> + <ListItemText primary={member.username} /> + <Button onClick={() => handleAddMember(member)}>Ajouter</Button> + </ListItem> + ))} + </List> + <Typography variant="subtitle1" sx={{ marginTop: 2 }}>Membres Sélectionnés</Typography> + <List dense> + {selectedMembers.map((member, index) => ( + <ListItem key={index}> + <ListItemText primary={member.username} /> + <Button onClick={() => handleRemoveMember(member)}>Retirer</Button> + </ListItem> + ))} + </List> + <Box sx={{ display: 'flex', justifyContent: 'flex-end', marginTop: 3 }}> + <Button variant="contained" color="primary" onClick={handleCreateTeam}> + {`Créer l'Équipe`} + </Button> + <Button variant="outlined" sx={{ marginLeft: 2 }} onClick={handleCloseModal}> + Annuler + </Button> + </Box> + </Box> + </Modal> + + {/* Modal Validate Teams */} + <Modal open={showModal === 'validateTeams'} onClose={handleCloseModal} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '60%', bgcolor: 'background.paper', padding: 3 }}> + <Typography variant="h6" sx={{ marginBottom: 2 }}>Validation des Équipes</Typography> + {teams.map((team, index) => ( + <Box key={index} sx={{ marginBottom: 2 }}> + <Typography variant="subtitle1">{team.name}</Typography> + <Divider sx={{ marginBottom: 1 }} /> + {team.members.map((member, idx) => ( + <Typography key={idx} variant="body2">{member.username}</Typography> + ))} + </Box> + ))} + <Box sx={{ display: 'flex', justifyContent: 'flex-end', marginTop: 3 }}> + <Button variant="contained" color="primary" onClick={handleCloseModal}> + Confirmer + </Button> + <Button variant="outlined" sx={{ marginLeft: 2 }} onClick={handleCloseModal}> + Annuler + </Button> + </Box> + </Box> + </Modal> + + {/* Modal Affect Projects */} + <Modal open={showModal === 'affectProjects'} onClose={handleCloseModal} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '60%', bgcolor: 'background.paper', padding: 3, maxHeight: '80vh', overflowY: 'auto' }}> + <Typography variant="h6" sx={{ marginBottom: 2 }}>Affectation des Projets</Typography> + {teams.map((team, index) => ( + <Box key={index} sx={{ marginBottom: 2 }}> + <Typography variant="subtitle1">{team.name}</Typography> + <Divider sx={{ marginBottom: 1 }} /> + <Box sx={{ marginBottom: 1 }}> + <Typography variant="body2" sx={{ display: 'inline', marginRight: 1 }}>Guide:</Typography> + <Button onClick={handleAddGuide} disabled={guidesAdded[team.name] || false} variant="outlined" size="small"> + {guidesAdded[team.name] ? 'Guide ajouté' : 'Ajouter un guide'} + </Button> + </Box> + <Box sx={{ marginBottom: 1 }}> + <Typography variant="body2" sx={{ display: 'inline', marginRight: 1 }}>Tests:</Typography> + <Button onClick={handleOpenModal('addTest', team)} variant="outlined" size="small"> + Ajouter un test + </Button> + <List dense> + {team.tests.map((test, idx) => ( + <ListItem key={idx}> + <ListItemText primary={test.title} /> + </ListItem> + ))} + </List> + </Box> + </Box> + ))} + <Box sx={{ display: 'flex', justifyContent: 'flex-end', marginTop: 3 }}> + <Button variant="contained" color="primary" onClick={handleAffectProjectsConfirm}> + Confirmer + </Button> + <Button variant="outlined" sx={{ marginLeft: 2 }} onClick={handleAffectProjectsCancel}> + Annuler + </Button> + </Box> + </Box> + </Modal> + + {/* Modal Add Test */} + <Modal open={showModal === 'addTest'} onClose={handleCloseModal} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + <Box sx={{ width: '40%', bgcolor: 'background.paper', padding: 3 }}> + <Typography variant="h6" sx={{ marginBottom: 2 }}>Ajouter un Test</Typography> + <List dense> + {testsData.map((test, index) => ( + <ListItem key={index}> + <ListItemText primary={test.title} /> + <Button onClick={() => handleAddTest(test)}>Ajouter</Button> + </ListItem> + ))} + </List> + <Box sx={{ display: 'flex', justifyContent: 'flex-end', marginTop: 3 }}> + <Button variant="contained" color="primary" onClick={handleCloseModal}> + Fermer + </Button> + </Box> + </Box> + </Modal> + + {/* Dropdown for Team Selection */} + <FormControl fullWidth variant="outlined" sx={{ marginTop: 2 }}> + <InputLabel id="team-select-label">Sélectionnez une équipe</InputLabel> + <Select + labelId="team-select-label" + value={selectedTeam} + onChange={handleTeamChange} + label="Sélectionnez une équipe" + > + {teams.map((team, index) => ( + <MenuItem key={index} value={team.name}> + {team.name} + </MenuItem> + ))} + </Select> + </FormControl> + + {/* Show the TeamSpace component if a team is selected */} + {selectedTeam && (<> + <TeamSpace + /> + </> + + )} + </Box> + </DashboardContent> + ); +}; + +export default CompetitionPage; diff --git a/src/shared/sections/events/hackathons/view/view.tsx b/src/shared/sections/events/hackathons/view/view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..61445aa2240fceade30760c9e455c9e7dca49a73 --- /dev/null +++ b/src/shared/sections/events/hackathons/view/view.tsx @@ -0,0 +1,506 @@ +'use client'; + +import React, { useState } from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import { styled } from '@mui/material/styles'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import { Event, eventsList, Session, Speaker } from 'src/shared/_mock/_event'; +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import Modal from '@mui/material/Modal'; +import TextField from '@mui/material/TextField'; +import { ArrowForwardIos, ArrowBackIos, LinkedIn } from '@mui/icons-material'; +import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; +import { HackathonDashboard } from 'src/shared/components/event/hackathonDashboard'; +import Card from '@mui/material/Card'; +import Switch from '@mui/material/Switch'; +import FormControlLabel from '@mui/material/FormControlLabel'; + + +// Styled Components +const ArchiverButton = styled(Button)(({ theme }) => ({ + position: 'fixed', + bottom: theme.spacing(2), + left: '50%', + transform: 'translateX(-50%)', + backgroundColor: 'grey', + color: 'white', + boxShadow: theme.shadows[4], +})); + +const SwitchCard = styled(Card)(({ theme }) => ({ + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + padding: theme.spacing(1), + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, + marginTop: theme.spacing(2), + backgroundColor: theme.palette.background.paper, +})); + +const ArchiveModalContent = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '400px', + maxWidth: '90%', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + textAlign: 'center', +})); + +const Div = styled('div')(({ theme }) => ({ + ...theme.typography.button, + backgroundColor: theme.palette.background.paper, + padding: theme.spacing(1), +})); + +const NavigationButton = styled(Button)(({ theme }) => ({ + position: 'absolute', + top: '50%', + transform: 'translateY(-50%)', + zIndex: 1, +})); + +const AnnouncementsPanel = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[1], + overflowY: 'auto', + maxHeight: 200, + position: 'relative', +})); + +const SpeakerCard = styled(Box)(({ theme }) => ({ + width: '100%', + height: 200, + backgroundSize: 'cover', + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-end', + textAlign: 'center', + margin: 0, + backgroundPosition: 'center', + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[2], +})); + +const AddActivityModal = styled(Modal)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const ModalContent = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '400px', + maxWidth: '90%', +})); + +const AccordionContainer = styled(Box)(({ theme }) => ({ + maxHeight: '400px', + overflowY: 'auto', + marginTop: theme.spacing(3), +})); + +const FloatingButton = styled(Button)(({ theme }) => ({ + position: 'absolute', + top: theme.spacing(2), + right: theme.spacing(2), + backgroundColor: 'white', + color: 'black', + borderRadius: '50%', + boxShadow: theme.shadows[4], + width: '36px', + height: '36px', + minWidth: 'unset', + fontSize: '24px', + padding: 0, + '&:hover': { + backgroundColor: 'white', + boxShadow: theme.shadows[6], + }, +})); + +const ScrollableBox = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.grey[200], + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + maxHeight: '80vh', + overflowY: 'auto', + width: '100%', +})); + +const DashboardModal = styled(Modal)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const AnnouncementModal = styled(Modal)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const ModalPlaceholder = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + width: '400px', + maxWidth: '90%', +})); + +type Props = { + eventId: { id: number }; +}; + +export function HackthonHome({ eventId }: Props) { + const event: Event = eventsList.find((e) => e.id === eventId.id) || eventsList[0]; + const speakers = event?.speakers || []; + + + const [currentPage, setCurrentPage] = useState(0); + const itemsPerPage = 5; + const totalPages = Math.ceil(speakers.length / itemsPerPage); + + const [announcementsExpanded, setAnnouncementsExpanded] = useState(true); + const [modalOpen, setModalOpen] = useState(false); + const [dashboardModalOpen, setDashboardModalOpen] = useState(false); + const [isSession, setIsSession] = useState(false); + const [selectedDateTime, setSelectedDateTime] = useState<Dayjs | null>(null); + const [activityTitle, setActivityTitle] = useState(''); + const [activityDescription, setActivityDescription] = useState(''); + const [announcementModalOpen, setAnnouncementModalOpen] = useState(false); + const [announcementTitle, setAnnouncementTitle] = useState(''); + const [announcementContent, setAnnouncementContent] = useState(''); + const [archiveModalOpen, setArchiveModalOpen] = useState(false); + const [isPublic, setIsPublic] = useState(true); + + const toggleAnnouncements = () => setAnnouncementsExpanded(!announcementsExpanded); + + const handlePrev = () => { + setCurrentPage((prev) => Math.max(prev - 1, 0)); +}; + +const handleNext = () => { + setCurrentPage((prev) => Math.min(prev + 1, totalPages - 1)); +}; + +const visibleSpeakers = speakers.slice(currentPage * itemsPerPage, (currentPage + 1) * itemsPerPage); + +const handleSessionClick = (session: Session) => { + const now = new Date(); + const sessionTime = new Date(session.time); + if (now >= sessionTime && session.roomLink) { + window.location.href = session.roomLink; + } +}; + + const handleOpenModal = () => { + setModalOpen(true); + }; + + const handleCloseModal = () => { + setModalOpen(false); + setIsSession(false); + setSelectedDateTime(null); + setActivityTitle(''); + setActivityDescription(''); + }; + + const handleOpenDashboardModal = () => { + setDashboardModalOpen(true); + }; + + const handleCloseDashboardModal = () => { + setDashboardModalOpen(false); + }; + + const handleOpenAnnouncementModal = () => { + setAnnouncementModalOpen(true); + }; + + const handleCloseAnnouncementModal = () => { + setAnnouncementModalOpen(false); + setAnnouncementTitle(''); + setAnnouncementContent(''); + }; + + const handleDateTimeChange = (newValue: Dayjs | null) => { + setSelectedDateTime(newValue); + }; + const handleFormSubmit = () => { + if (activityTitle && selectedDateTime) { + const selectedSpeakerId = isSession ? parseInt((document.getElementById('speakerSelect') as HTMLSelectElement).value, 10) : null; + const selectedSpeaker: Speaker | null = isSession && selectedSpeakerId + ? speakers.find((speaker) => speaker.id === selectedSpeakerId) || null + : null; + const newSession: Session = { + id: Date.now(), + title: activityTitle, + description: activityDescription, + time: selectedDateTime.toISOString(), + speaker: selectedSpeaker, + roomLink: '', + }; + + if (event?.sessions) { + event.sessions.push(newSession); + } else { + event.sessions = [newSession]; + } + handleCloseModal(); + } +}; + + + + const handleAnnouncementSubmit = () => { + console.log('Announcement added:', { title: announcementTitle, content: announcementContent }); + handleCloseAnnouncementModal(); + }; + + const handleArchiveModalOpen = () => setArchiveModalOpen(true); + const handleArchiveModalClose = () => setArchiveModalOpen(false); + + const handleArchiveConfirm = () => { + // Logic to archive the event + console.log('Event archived'); + setArchiveModalOpen(false); + }; + + const handleSwitchChange = (_event: React.ChangeEvent<HTMLInputElement>) => { + setIsPublic(_event.target.checked); + }; + + return ( + <DashboardContent maxWidth="xl"> + + <Box display="flex" justifyContent="space-between" alignItems="center"> + <Div>{event?.name}</Div> + <Button onClick={handleArchiveModalOpen} color="error">Archiver</Button> + <SwitchCard> + <FormControlLabel + control={<Switch checked={isPublic} onChange={handleSwitchChange} />} + label={isPublic ? "Publique" : "Privée"} + sx={{ color: isPublic ? 'green' : 'blue' }} + /> + </SwitchCard> + </Box> + <Box display="flex" justifyContent="space-between" alignItems="center" mt={3}> + <Button onClick={handleOpenDashboardModal}>Statistiques</Button> + <Button onClick={handleOpenModal}>Ajouter une activité</Button> + <Button href={`/dashboard/events/Administration/hackathons/${event.id}/competition`}>Compétition</Button> + </Box> + + {announcementsExpanded && ( + <AnnouncementsPanel> + <Typography variant="h6">Annonces</Typography> + <Typography variant="body2">Voici quelques annonces importantes...</Typography> + <FloatingButton onClick={handleOpenAnnouncementModal}>+</FloatingButton> + </AnnouncementsPanel> + )} + + <Box mt={5}> + <Typography variant="h6">Chronologie</Typography> + <AccordionContainer> + {event?.sessions?.map((session) => ( + <Accordion key={session.id}> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Box sx={{ display: 'flex', width: '100%' }}> + <Typography sx={{ flex: 1 }}> + {new Date(session.time).toLocaleDateString()} | {new Date(session.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} + </Typography> + <Typography sx={{ flex: 2, textAlign: 'center' }}> + {session.title} {session.speaker ? "| ".concat(session.speaker.name) : null} + </Typography> + </Box> + </AccordionSummary> + <AccordionDetails> + <Typography>{session.description}</Typography> + {session.speaker && ( + <Button onClick={() => handleSessionClick(session)} startIcon={<ArrowForwardIos />}> + Participer + </Button> + )} + </AccordionDetails> + </Accordion> + ))} + </AccordionContainer> + </Box> + + <Box mt={5}> + <Typography variant="h6">Intervenants</Typography> + <Box sx={{ display: 'flex', overflow: 'hidden', position: 'relative' }}> + {currentPage > 0 && ( + <NavigationButton onClick={handlePrev} sx={{ left: '-30px' }}> + <ArrowBackIos /> + </NavigationButton> + )} + + <Box sx={{ display: 'flex', justifyContent: 'space-between', overflow: 'hidden', width: '100%' }}> + {visibleSpeakers.map((speaker: Speaker) => ( + <SpeakerCard + key={speaker.id} + sx={{ backgroundImage: `url(${speaker.imageUrl})`, backgroundSize: 'cover' }} + > + <Box + sx={{ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + padding: '5px', + }} + > + <Box sx={{ textAlign: 'left', color: 'white' }}> + <Typography variant="subtitle1">{speaker.name}</Typography> + <Typography variant="caption">{speaker.role}</Typography> + </Box> + <Button + href='speaker.linkedinUrl' + target="_blank" + > + <LinkedIn /> + </Button> + </Box> + </SpeakerCard> + ))} + </Box> + + {currentPage < totalPages - 1 && ( + <NavigationButton onClick={handleNext} sx={{ right: '-30px' }}> + <ArrowForwardIos /> + </NavigationButton> + )} + </Box> + </Box> + + <DashboardModal open={dashboardModalOpen} onClose={handleCloseDashboardModal}> + <ScrollableBox> + <HackathonDashboard /> + </ScrollableBox> + </DashboardModal> + + <AddActivityModal open={modalOpen} onClose={handleCloseModal}> + <ModalContent> + <Typography variant="h6">Ajouter une activité</Typography> + <DateTimePicker + label="Date & Heure" + value={selectedDateTime} + onChange={handleDateTimeChange} + /> + <TextField + label="Titre de l'activité" + fullWidth + margin="normal" + value={activityTitle} + onChange={(e) => setActivityTitle(e.target.value)} + /> + <TextField + label="Description de l'activité" + fullWidth + margin="normal" + multiline + rows={4} + value={activityDescription} + onChange={(e) => setActivityDescription(e.target.value)} + /> + <FormControlLabel + control={ + <Switch + checked={isSession} + onChange={(e) => setIsSession(e.target.checked)} + /> + } + label="Est-ce une session?" + /> + {isSession && ( + <TextField + id="speakerSelect" + label="Intervenants" + fullWidth + margin="normal" + select + SelectProps={{ + native: true, + }} + > + {event?.speakers?.map((speaker: Speaker) => ( + <option key={speaker.id} value={speaker.id}> + {speaker.name} + </option> + ))} + </TextField> +)} + + <Box mt={2} textAlign="right"> + <Button onClick={handleCloseModal} sx={{ marginRight: 2 }}>Annuler</Button> + <Button onClick={handleFormSubmit} variant="contained" sx={{ backgroundColor: '#333', color: '#fff' }}>Ajouter</Button> + </Box> + </ModalContent> + </AddActivityModal> + + <AnnouncementModal open={announcementModalOpen} onClose={handleCloseAnnouncementModal}> + <ModalPlaceholder> + <Typography variant="h6" component="h2" gutterBottom> + Nouvelle annonce + </Typography> + <TextField + label="Titre" + value={announcementTitle} + onChange={(e) => setAnnouncementTitle(e.target.value)} + fullWidth + margin="normal" + /> + <TextField + label="Contenu" + value={announcementContent} + onChange={(e) => setAnnouncementContent(e.target.value)} + fullWidth + margin="normal" + multiline + rows={4} + /> + <Box mt={3} display="flex" justifyContent="flex-end"> + <Button onClick={handleAnnouncementSubmit} variant="contained" color="primary"> + Ajouter + </Button> + </Box> + </ModalPlaceholder> + </AnnouncementModal> + + <Modal open={archiveModalOpen} onClose={handleArchiveModalClose}> + <ArchiveModalContent> + <Typography variant="h6" gutterBottom> + {`Voulez-vous terminer l'événement et le déplacer à l'archivage?`} + </Typography> + <Box mt={3} display="flex" justifyContent="space-around"> + <Button onClick={handleArchiveModalClose} variant="outlined" color="secondary"> + Non + </Button> + <Button onClick={handleArchiveConfirm} variant="contained" color="error"> + Oui + </Button> + </Box> + </ArchiveModalContent> + </Modal> + + </DashboardContent> + ); +} diff --git a/src/shared/sections/events/room/liveChat/liveChat.tsx b/src/shared/sections/events/room/liveChat/liveChat.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3b09ac84b300c3290b656678c718f652dd29c951 --- /dev/null +++ b/src/shared/sections/events/room/liveChat/liveChat.tsx @@ -0,0 +1,55 @@ + +import React from 'react'; +import { Box, Typography, TextField } from '@mui/material'; +import { ChatMessage } from 'src/shared/_mock/_room'; + +interface LiveChatProps { + chatMessages: ChatMessage[]; + userId: string; + onSendMessage: (event: React.KeyboardEvent<HTMLInputElement>) => void; + newMessage: string; + setNewMessage: (message: string) => void; +} +export default function LiveChat({ chatMessages, userId, onSendMessage, newMessage, setNewMessage }:LiveChatProps) { + return ( + <Box + display="flex" + flexDirection="column" + width="300px" + height="400px" + bgcolor="white" + borderColor="black" + border={1} + borderRadius={2} + boxShadow={3} + p={2} + position="relative" + > + <Box flexGrow={1} overflow="auto"> + {chatMessages.map((msg) => ( + <Box key={msg.id} display="flex" flexDirection="column" mb={1}> + <Typography variant="body2" color="black"> + {msg.message} + </Typography> + </Box> + ))} + </Box> + <TextField + fullWidth + variant="outlined" + size="small" + value={newMessage} + onChange={(e) => setNewMessage(e.target.value)} + onKeyDown={onSendMessage} + placeholder="Type a message..." + InputProps={{ + style: { + color: 'black', + borderColor: 'black', + }, + }} + /> + </Box> + ); +} + diff --git a/src/shared/sections/events/room/rooms-administration.tsx b/src/shared/sections/events/room/rooms-administration.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ebf103eb42f1f72d40cf0ebb36067c0a478f0b5a --- /dev/null +++ b/src/shared/sections/events/room/rooms-administration.tsx @@ -0,0 +1,126 @@ +'use client'; + +import React, { useState } from 'react'; +import { Box, Card, CardContent, Typography, IconButton, Button, Modal } from '@mui/material'; +import PeopleIcon from '@mui/icons-material/People'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import { styled } from '@mui/material/styles'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import RoomAdministration from 'src/shared/components/event/roomAdministration'; +import Link from 'next/link'; +import { paths } from 'src/routes/paths'; +import { Room ,sampleRooms} from 'src/shared/_mock/_room'; + + +interface User { + id: string; + name: string; + role: string; + permissions: { [key: string]: boolean }; +} + +interface RoomsProps { + rooms: Room[]; +} + +export function Rooms() { + + const [filter, setFilter] = useState<'live' | 'ended' | 'en attente' | 'all'>('all'); + const [selectedRoom, setSelectedRoom] = useState<Room | null>(null); + const [deroulementModalOpen, setDeroulementModalOpen] = useState<string | null>(null); + + + const handleFilterChange = (status: 'live' | 'ended' | 'en attente' | 'all') => { + setFilter(status); + }; + + + const handleOpenDeroulementModal = (id: string) => { + setDeroulementModalOpen(id); + }; + + const handleCloseDeroulementModal = () => { + setDeroulementModalOpen(null); + }; + const filteredRooms = sampleRooms.filter(room => filter === 'all' || room.status === filter); + + return ( + <DashboardContent maxWidth="xl"> + <Box> + <Box display="flex" justifyContent="space-between" padding={2}> + <Button variant={filter === 'all' ? 'contained' : 'outlined'} onClick={() => handleFilterChange('all')}>Tous</Button> + <Button variant={filter === 'live' ? 'contained' : 'outlined'} onClick={() => handleFilterChange('live')}>En directe</Button> + <Button variant={filter === 'ended' ? 'contained' : 'outlined'} onClick={() => handleFilterChange('ended')}>Terminée</Button> + <Button variant={filter === 'en attente' ? 'contained' : 'outlined'} onClick={() => handleFilterChange('en attente')}>En Attente</Button> + </Box> + <Box> + {filteredRooms.map(room => ( + <Card key={room.id} sx={{ mb: 2, borderColor: 'black', border: 1, borderRadius: 2 }}> + <CardContent> + <Typography variant="h6">{room.title}</Typography> + <Typography variant="body2">Speakers: {room.speakers.join(', ')}</Typography> + <Typography variant="body2">Participation Criteria: {room.participationCriteria}</Typography> + <Typography variant="body2">{room.date} at {room.time}</Typography> + <Box display="flex" justifyContent="space-between" mt={2}> + + + <Button variant="outlined" startIcon={<ExitToAppIcon />}> + <Link + href={`${paths.dashboard.rooms}/${room.id}`} + color="inherit" + > + Rejoindre le salon + </Link> + </Button> + + <Button + variant="outlined" + color="secondary" + size="small" + onClick={() => handleOpenDeroulementModal(room.id)} + sx={{ mt: 2 }} + > + Gérer + </Button> + </Box> + </CardContent> + </Card> + ))} + </Box> + + + </Box> + +<Modal + open={deroulementModalOpen !== null} + onClose={handleCloseDeroulementModal} + aria-labelledby="modal-deroulement-title" + aria-describedby="modal-deroulement-description" + > + <Box + sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxHeight: '80%', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + borderRadius: 2, + overflowY: 'auto', + }} + > + <RoomAdministration /> + </Box> + + </Modal> + </DashboardContent> + + ); +}; + + diff --git a/src/shared/sections/events/room/view/view.tsx b/src/shared/sections/events/room/view/view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6657ef6a961e4ca53e7340c1e38b5d0ed0ae1158 --- /dev/null +++ b/src/shared/sections/events/room/view/view.tsx @@ -0,0 +1,376 @@ +"use client"; + +import React, { useState } from 'react'; +import { Box, IconButton, Typography, Card, CardContent } from '@mui/material'; +import ChatIcon from '@mui/icons-material/Chat'; +import CameraAltIcon from '@mui/icons-material/CameraAlt'; +import ScreenShareIcon from '@mui/icons-material/ScreenShare'; +import MicIcon from '@mui/icons-material/Mic'; +import FullscreenIcon from '@mui/icons-material/Fullscreen'; +import SettingsIcon from '@mui/icons-material/Settings'; +import PeopleIcon from '@mui/icons-material/People'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; +import { User, ChatMessage } from 'src/shared/_mock/_room'; + + +import { styled, alpha } from '@mui/material/styles'; +import Button from '@mui/material/Button'; +import Menu, { MenuProps } from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; + +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import Switch, { SwitchProps } from '@mui/material/Switch'; +import Stack from '@mui/material/Stack'; +import { DashboardContent } from 'src/shared/layouts/dashboard'; +import LiveChat from '../liveChat/liveChat'; + + +const AntSwitch = styled(Switch)(({ theme }) => ({ + width: 28, + height: 16, + padding: 0, + display: 'flex', + '&:active': { + '& .MuiSwitch-thumb': { + width: 15, + }, + '& .MuiSwitch-switchBase.Mui-checked': { + transform: 'translateX(9px)', + }, + }, + '& .MuiSwitch-switchBase': { + padding: 2, + '&.Mui-checked': { + transform: 'translateX(12px)', + color: '#fff', + '& + .MuiSwitch-track': { + opacity: 1, + // backgroundColor: theme.palette.mode === 'dark' ? '#177ddc' : '#1890ff', + }, + }, + }, + '& .MuiSwitch-thumb': { + boxShadow: '0 2px 4px 0 rgb(0 35 11 / 20%)', + width: 12, + height: 12, + borderRadius: 6, + transition: theme.transitions.create(['width'], { + duration: 200, + }), + }, + '& .MuiSwitch-track': { + borderRadius: 16 / 2, + opacity: 1, + // backgroundColor: + // theme.palette.mode === 'dark' ? 'rgba(255,255,255,.35)' : 'rgba(0,0,0,.25)', + boxSizing: 'border-box', + }, +})); + +const StyledMenu = styled((props: MenuProps) => ( + <Menu + elevation={0} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + {...props} + /> +))(({ theme }) => ({ + '& .MuiPaper-root': { + borderRadius: 6, + marginTop: theme.spacing(1), + minWidth: 180, + color: + 'rgb(55, 65, 81)' + , + boxShadow: + 'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px', + '& .MuiMenu-list': { + padding: '4px 0', + }, + '& .MuiMenuItem-root': { + '& .MuiSvgIcon-root': { + fontSize: 18, + color: theme.palette.text.secondary, + marginRight: theme.spacing(1.5), + }, + '&:active': { + backgroundColor: alpha( + theme.palette.primary.main, + theme.palette.action.selectedOpacity + ), + }, + }, + }, +})); + + + + + +const initialUsers: User[] = [ + { id: 'admin', role: 'admin', name: 'Admin', permissions: { camera: true, screenSharing: true, microphone: true }, isScreenSharing: false, isActive: true, isCameraActive: false, isMicActive: false }, + { id: 'user1', role: 'user 1', name: 'User 1', permissions: { camera: true, screenSharing: true, microphone: true }, isScreenSharing: false, isActive: true, isCameraActive: false, isMicActive: false }, + { id: 'user2', role: 'user 2', name: 'User 2', permissions: { camera: true, screenSharing: true, microphone: true }, isScreenSharing: false, isActive: true, isCameraActive: false, isMicActive: false } +]; + +const initialChatMessages: ChatMessage[] = [ + { id: 1, userId: 'user1', message: 'Bonjour!' }, + { id: 2, userId: 'user2', message: 'Bienvenue dans le stream!' }, +]; + +const View: React.FC = () => { + + + + + + + const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent<HTMLElement>) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + + + + + const [isChatVisible, setIsChatVisible] = useState(false); + const [isUsersExpanded, setIsUsersExpanded] = useState(false); + const [chatMessages, setChatMessages] = useState<ChatMessage[]>(initialChatMessages); + const [currentUser, setCurrentUser] = useState<User>(initialUsers[0]); + const [users, setUsers] = useState<User[]>(initialUsers); + const [activeStream, setActiveStream] = useState<string | null>(null); + const [newMessage, setNewMessage] = useState(''); + + const handleChatToggle = () => { + setIsChatVisible(!isChatVisible); + }; + + const handleUsersExpand = () => { + setIsUsersExpanded(!isUsersExpanded); + }; + + const handleUserChange = (userId: string) => { + const user = users.find((u) => u.id === userId); + if (user) setCurrentUser(user); + }; + + const handleUserActionToggle = (action: 'isScreenSharing' | 'isCameraActive' | 'isMicActive', userId: string) => { + setUsers(users.map(user => { + if (user.id === userId) { + return { + ...user, + [action]: !user[action] + }; + } + return user; + })); + }; + + const handleKickUser = (userId: string) => { + setUsers(users.filter(user => user.id !== userId)); + if (currentUser.id === userId) { + setCurrentUser(users[0]); + } + }; + + const handleStreamClick = (userId: string) => { + setActiveStream(userId); + }; + + const handleSendMessage = (event: React.KeyboardEvent<HTMLInputElement>) => { + if (event.key === 'Enter' && newMessage.trim()) { + setChatMessages([...chatMessages, { id: chatMessages.length + 1, userId: currentUser.id, message: newMessage }]); + setNewMessage(''); + } + }; + + return ( + <DashboardContent > + <Box display="flex" justifyContent="space-between" width="100%" padding={2} position="absolute" zIndex={50}> + <Box display="flex" alignItems="center"> + {users.map((user) => ( + <IconButton + key={user.id} + color={currentUser.id === user.id ? 'primary' : 'default'} + onClick={() => handleUserChange(user.id)} + > + <Typography >{user.name}</Typography> + </IconButton> + ))} + </Box> + + </Box> + + <Box sx={{ left: '50%' , transform: 'translateX(-50%)' ,display:"flex" ,justifyContent:"center" ,flexDirection:"column" ,alignItems:"center" ,width:"100%" ,height:"100%" }}> + {activeStream ? ( + <Card sx={{ width: '100%', height: '100%', borderColor: 'black', border: 1, borderRadius: 2 }}> + <CardContent> + <Box display="flex" alignItems="center" justifyContent="center" flexDirection="column"> + <Typography variant="h5" >{`User ${activeStream} Stream`}</Typography> + <IconButton onClick={() => setActiveStream(null)}> + <FullscreenIcon /> + </IconButton> + </Box> + </CardContent> + </Card> + ) : ( + <Typography variant="h5" >No Active Stream</Typography> + )} + <Box flex="end" justifyContent="center" mt={2} overflow="auto"> + + {users.filter(user => user.isScreenSharing).map(user => ( + <Card + key={user.id} + onClick={() => handleStreamClick(user.id)} + sx={{ m: 1, borderColor: 'white', border: 1, borderRadius: 2, cursor: 'pointer' }} + > + <CardContent> + <Typography >{`User ${user.id} Screen`}</Typography> + </CardContent> + </Card> + ))} + </Box> + </Box> + + + <Box position="fixed" bottom={16} right={16} zIndex={10}> + <IconButton onClick={handleChatToggle}> + <ChatIcon /> + </IconButton> + {isChatVisible && ( + <LiveChat chatMessages={chatMessages} userId={currentUser.id} onSendMessage={handleSendMessage} newMessage={newMessage} setNewMessage={setNewMessage} /> + )} + </Box> + + <Box position="fixed" bottom={16} zIndex={10}> + <IconButton onClick={handleUsersExpand}> + <PeopleIcon /> + </IconButton> + {isUsersExpanded && ( + <Box display="flex" justifyContent="space-between" flexDirection="column" border={1} borderRadius={2} boxShadow={3} padding={2}> + {users.map(user => ( + <Card + key={user.id} + sx={{ + mb: 1, + border: user.isMicActive ? '2px solid green' : '1px solid black', + + borderColor:user.isMicActive ?'green' : 'black', + borderRadius: 2, + cursor: 'pointer' + }} + > + <CardContent> + + <Box display="flex" justifyContent="center"> + <Typography >{user.name}</Typography> + {user.isMicActive && <MicIcon />} + </Box> + + + {currentUser.role === 'admin' && ( + <Box display="flex" justifyContent="center"> + <> + <Button + id="demo-customized-button" + aria-controls={open ? 'demo-customized-menu' : undefined} + aria-haspopup="true" + aria-expanded={open ? 'true' : undefined} + variant="contained" + disableElevation + onClick={handleClick} + endIcon={<KeyboardArrowDownIcon />} + >Permissions</Button> + <StyledMenu + id="demo-customized-menu" + MenuListProps={{ + 'aria-labelledby': 'demo-customized-button', + }} + anchorEl={anchorEl} + open={open} + onClose={handleClose} + > + <MenuItem> + <CameraAltIcon /> + <Stack direction="row" spacing={1} alignItems="center"> + <Typography>Off</Typography> + <AntSwitch + defaultChecked + inputProps={{ 'aria-label': 'ant design' }} + /> + <Typography>On</Typography> + </Stack> + </MenuItem> + <MenuItem > + <MicIcon /> + <Stack direction="row" spacing={1} alignItems="center"> + <Typography>Off</Typography> + <AntSwitch + defaultChecked + inputProps={{ 'aria-label': 'ant design' }} + /> + <Typography>On</Typography> + </Stack> + </MenuItem> + <MenuItem > + <ScreenShareIcon /> + <Stack direction="row" spacing={1} alignItems="center"> + <Typography>Off</Typography> + <AntSwitch + defaultChecked + inputProps={{ 'aria-label': 'ant design' }} + /> + <Typography>On</Typography> + </Stack> + </MenuItem> + </StyledMenu> + </> + <IconButton onClick={() => handleKickUser(user.id)}> + <ExitToAppIcon /> + </IconButton> + </Box> + )} + + + + </CardContent> + </Card> + ))} + </Box> + )} + </Box> + <Box sx={{position:"fixed" , bottom:16, left: '50%' , transform: 'translateX(-50%)' ,display:"flex" ,justifyContent:"center", mt:2 }}> + + <IconButton color={currentUser.isCameraActive ? 'primary' : 'default'} disabled={!currentUser.permissions.camera} onClick={() => handleUserActionToggle('isCameraActive', currentUser.id)}> + <CameraAltIcon /> + </IconButton> + <IconButton color={currentUser.isScreenSharing ? 'primary' : 'default'} disabled={!currentUser.permissions.screenSharing} onClick={() => handleUserActionToggle('isScreenSharing', currentUser.id)}> + <ScreenShareIcon /> + </IconButton> + <IconButton color={currentUser.isMicActive ? 'primary' : 'default'} disabled={!currentUser.permissions.microphone} onClick={() => handleUserActionToggle('isMicActive', currentUser.id)}> + <MicIcon /> + </IconButton> + {currentUser.role !== 'admin' && ( + <IconButton onClick={() => handleKickUser(currentUser.id)} > + <ExitToAppIcon /> + </IconButton> + )} + + </Box> + </DashboardContent> + + ); +}; + +export default View; diff --git a/yarn.lock b/yarn.lock index 310112766c357c0f1d1b1205fbae0c643a22dfe7..971e8cee6f151a6d1fd7ccd9b53b4fa5c2653fe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1808,6 +1808,18 @@ dependencies: "@emotion/memoize" "^0.9.0" +"@emotion/is-prop-valid@1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + "@emotion/memoize@^0.9.0": version "0.9.0" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" @@ -1860,6 +1872,11 @@ resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz" integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== +"@emotion/unitless@0.8.1": + version "0.8.1" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + "@emotion/use-insertion-effect-with-fallbacks@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz" @@ -4276,6 +4293,57 @@ resolved "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.142.tgz" integrity sha512-wy2y/2hQKrS6myOS++koXg3N1Hg+LLyPjaggCFajczSHZPqBnOMuT2sdH3kiASrmdBYyM3pmjyz5SoWraRllCQ== +"@types/d3-array@^3.0.3": + version "3.2.1" + resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.0" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== + +"@types/d3-scale@^4.0.2": + version "4.0.8" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.6" + resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + "@types/debug@^4.0.0", "@types/debug@^4.1.7": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" @@ -4429,6 +4497,11 @@ resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.6.tgz" integrity sha512-4nebF2ZJGzQk0ka0O6+FZUWceyFv4vWq/0dXBMmrSeAwzOuOd/GxE5Pa64d/ndeNLG73dXoBsRzvtsVsYUv6Uw== +"@types/stylis@4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz" + integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw== + "@types/trusted-types@^2.0.2": version "2.0.7" resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz" @@ -5629,6 +5702,11 @@ camelcase@^6.2.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== + can-use-dom@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz" @@ -5755,7 +5833,7 @@ clsx@^1.2.1: resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== -clsx@^2.1.0, clsx@^2.1.1: +clsx@^2.0.0, clsx@^2.1.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -5920,6 +5998,11 @@ crypto-js@^4.2.0: resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz" integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== + css-select@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz" @@ -5931,6 +6014,15 @@ css-select@^5.1.0: domutils "^3.0.1" nth-check "^2.0.1" +css-to-react-native@3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@^2.2.1: version "2.3.1" resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz" @@ -5969,11 +6061,82 @@ csso@5.0.5: dependencies: css-tree "~2.2.0" -csstype@^3.0.2, csstype@^3.1.3: +csstype@^3.0.2, csstype@^3.1.3, csstype@3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +d3-array@^3.1.6, "d3-array@2 - 3", "d3-array@2.10.0 - 3": + version "3.2.4" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3": + version "3.0.1" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +d3-time@^3.0.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3": + version "3.1.0" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" @@ -6040,6 +6203,11 @@ decamelize@^1.2.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-named-character-reference@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz" @@ -6763,7 +6931,7 @@ ethers@^6.8.0: tslib "2.4.0" ws "8.17.1" -eventemitter3@^4.0.7: +eventemitter3@^4.0.1, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -6823,6 +6991,11 @@ fast-diff@^1.1.2: resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== +fast-equals@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz" + integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== + fast-glob@^3.2.9, fast-glob@^3.3.1: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" @@ -7545,6 +7718,11 @@ internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + iron-webcrypto@^1.1.1: version "1.2.1" resolved "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz" @@ -9352,7 +9530,7 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.2.0" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== @@ -9375,6 +9553,15 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@8.4.38: + version "8.4.38" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + potpack@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz" @@ -9710,7 +9897,7 @@ react-copy-to-clipboard@^5.1.0: copy-to-clipboard "^3.3.1" prop-types "^15.8.1" -"react-dom@^16.11.0 || ^17 || ^18", "react-dom@^16.7.0 || ^17 || ^18 || ^19", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@^18.3.1, "react-dom@>= 16.12.0", react-dom@>=16.3.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=17, "react-dom@15 - 18": +"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.11.0 || ^17 || ^18", "react-dom@^16.7.0 || ^17 || ^18 || ^19", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@^18.3.1, "react-dom@>= 16.12.0", "react-dom@>= 16.8.0", react-dom@>=16.3.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=17, "react-dom@15 - 18": version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -9756,6 +9943,11 @@ react-innertext@^1.1.5: resolved "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz" integrity sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q== +react-is@^16.10.2: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" @@ -9838,6 +10030,15 @@ react-phone-number-input@^3.4.3: libphonenumber-js "^1.11.2" prop-types "^15.8.1" +react-smooth@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz" + integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" @@ -9848,7 +10049,7 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -"react@^15.3.0 || 16 || 17 || 18", "react@^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x", "react@^16.11.0 || ^17 || ^18", "react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.7.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.1 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16.12.0", "react@>= 16.8 || 18.0.0", "react@>= 16.8.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=0.0.0 <=99", react@>=0.13, react@>=16, react@>=16.3.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=17, react@>=17.0.0, react@>=18, "react@15 - 18": +"react@^15.3.0 || 16 || 17 || 18", "react@^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.11.0 || ^17 || ^18", "react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.7.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.1 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16.12.0", "react@>= 16.8 || 18.0.0", "react@>= 16.8.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=0.0.0 <=99", react@>=0.13, react@>=16, react@>=16.3.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=17, react@>=17.0.0, react@>=18, "react@15 - 18": version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -9876,6 +10077,27 @@ real-require@^0.1.0: resolved "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz" integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== + dependencies: + decimal.js-light "^2.4.1" + +recharts@^2.12.7: + version "2.12.7" + resolved "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz" + integrity sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ== + dependencies: + clsx "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.21" + react-is "^16.10.2" + react-smooth "^4.0.0" + recharts-scale "^0.4.4" + tiny-invariant "^1.3.1" + victory-vendor "^36.6.8" + reflect.getprototypeof@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz" @@ -10233,6 +10455,11 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" +shallowequal@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -10487,6 +10714,21 @@ style-to-object@^1.0.0: dependencies: inline-style-parser "0.2.3" +styled-components@^6.1.12: + version "6.1.12" + resolved "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz" + integrity sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA== + dependencies: + "@emotion/is-prop-valid" "1.2.2" + "@emotion/unitless" "0.8.1" + "@types/stylis" "4.2.5" + css-to-react-native "3.2.0" + csstype "3.1.3" + postcss "8.4.38" + shallowequal "1.1.0" + stylis "4.3.2" + tslib "2.6.2" + styled-jsx@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz" @@ -10501,7 +10743,7 @@ stylis-plugin-rtl@^2.1.1: dependencies: cssjanus "^2.0.1" -stylis@^4.3.2, stylis@4.x: +stylis@^4.3.2, stylis@4.3.2, stylis@4.x: version "4.3.2" resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz" integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg== @@ -10663,6 +10905,11 @@ tiny-inflate@^1.0.0, tiny-inflate@^1.0.3: resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-lru@^11.2.11: version "11.2.11" resolved "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.11.tgz" @@ -10748,7 +10995,7 @@ tslib@^1.11.1: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2, tslib@2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -11146,6 +11393,26 @@ vfile@^6.0.0: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" +victory-vendor@^36.6.8: + version "36.9.2" + resolved "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz" + integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== + dependencies: + "@types/d3-array" "^3.0.3" + "@types/d3-ease" "^3.0.0" + "@types/d3-interpolate" "^3.0.1" + "@types/d3-scale" "^4.0.2" + "@types/d3-shape" "^3.1.0" + "@types/d3-time" "^3.0.0" + "@types/d3-timer" "^3.0.0" + d3-array "^3.1.6" + d3-ease "^3.0.1" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + d3-time "^3.0.0" + d3-timer "^3.0.1" + viem@^1.0.0, viem@^1.18.2, viem@>=0.3.35, "viem@>=1 <2": version "1.21.4" resolved "https://registry.npmjs.org/viem/-/viem-1.21.4.tgz"