eionet2-dashboard 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.fx/configs/azure.parameters.Prod_EEA.json +15 -0
  2. package/.fx/configs/azure.parameters.dev.json +15 -0
  3. package/.fx/configs/config.Prod_EEA.json +10 -0
  4. package/.fx/configs/config.dev.json +10 -0
  5. package/.fx/configs/projectSettings.json +83 -0
  6. package/.fx/states/state.Prod_EEA.json +47 -0
  7. package/.fx/states/state.dev.json +47 -0
  8. package/.vscode/launch.json +91 -0
  9. package/.vscode/settings.json +6 -0
  10. package/.vscode/tasks.json +63 -0
  11. package/CHANGELOG.md +140 -0
  12. package/Jenkinsfile +166 -0
  13. package/LICENSE.md +9 -0
  14. package/README.md +55 -0
  15. package/api/.funcignore +11 -0
  16. package/api/extensions.csproj +11 -0
  17. package/api/getGraphData/function.json +27 -0
  18. package/api/getGraphData/index.js +147 -0
  19. package/api/host.json +11 -0
  20. package/api/package-lock.json +1546 -0
  21. package/api/package.json +17 -0
  22. package/api/proxies.json +4 -0
  23. package/package.json +25 -0
  24. package/tabs/.env.teamsfx.Prod_EEA +11 -0
  25. package/tabs/.env.teamsfx.dev +11 -0
  26. package/tabs/.eslintrc.json +48 -0
  27. package/tabs/.prettierrc +7 -0
  28. package/tabs/.stylelintrc.json +6 -0
  29. package/tabs/babel.config.js +3 -0
  30. package/tabs/package-lock.json +15564 -0
  31. package/tabs/package.json +88 -0
  32. package/tabs/public/auth-end.html +76 -0
  33. package/tabs/public/auth-start.html +178 -0
  34. package/tabs/public/deploy.png +0 -0
  35. package/tabs/public/favicon.ico +0 -0
  36. package/tabs/public/hello.png +0 -0
  37. package/tabs/public/index.html +20 -0
  38. package/tabs/public/publish.png +0 -0
  39. package/tabs/src/components/App.jsx +36 -0
  40. package/tabs/src/components/CustomColumnResizeIcon.jsx +68 -0
  41. package/tabs/src/components/CustomDrawer.jsx +51 -0
  42. package/tabs/src/components/EventDialogTitle.jsx +29 -0
  43. package/tabs/src/components/HtmlBox.jsx +18 -0
  44. package/tabs/src/components/Privacy.jsx +17 -0
  45. package/tabs/src/components/ResizableGrid.jsx +44 -0
  46. package/tabs/src/components/Tab.jsx +477 -0
  47. package/tabs/src/components/Tab.scss +138 -0
  48. package/tabs/src/components/TabConfig.jsx +51 -0
  49. package/tabs/src/components/TabPanel.jsx +29 -0
  50. package/tabs/src/components/TermsOfUse.jsx +17 -0
  51. package/tabs/src/components/UnderConstruction.jsx +24 -0
  52. package/tabs/src/components/UserMenu.jsx +109 -0
  53. package/tabs/src/components/_variables.scss +10 -0
  54. package/tabs/src/components/activity/Activity.jsx +301 -0
  55. package/tabs/src/components/activity/ConsultationList.jsx +297 -0
  56. package/tabs/src/components/activity/EventList.jsx +463 -0
  57. package/tabs/src/components/activity/GroupsTags.jsx +26 -0
  58. package/tabs/src/components/activity/Reporting.jsx +13 -0
  59. package/tabs/src/components/activity/activity.scss +153 -0
  60. package/tabs/src/components/event_rating/EventRating.jsx +92 -0
  61. package/tabs/src/components/event_rating/EventRatingDialog.jsx +46 -0
  62. package/tabs/src/components/event_registration/Approval.jsx +80 -0
  63. package/tabs/src/components/event_registration/ApprovalDialog.jsx +30 -0
  64. package/tabs/src/components/event_registration/ApprovalList.jsx +62 -0
  65. package/tabs/src/components/event_registration/EventRegistration.jsx +214 -0
  66. package/tabs/src/components/lib/useData.js +33 -0
  67. package/tabs/src/components/lib/useGraph.js +39 -0
  68. package/tabs/src/components/lib/useTeamsFx.js +55 -0
  69. package/tabs/src/components/my_country/AtAGlance.jsx +151 -0
  70. package/tabs/src/components/my_country/CountryProgress.jsx +92 -0
  71. package/tabs/src/components/my_country/DataReporters.jsx +13 -0
  72. package/tabs/src/components/my_country/GroupView.jsx +54 -0
  73. package/tabs/src/components/my_country/GroupsBoard.jsx +52 -0
  74. package/tabs/src/components/my_country/IndicatorCard.jsx +60 -0
  75. package/tabs/src/components/my_country/ManagementBoard.jsx +109 -0
  76. package/tabs/src/components/my_country/MyCountry.jsx +186 -0
  77. package/tabs/src/components/my_country/ProgressGauge.jsx +125 -0
  78. package/tabs/src/components/my_country/ScientificCommittee.jsx +13 -0
  79. package/tabs/src/components/my_country/YearlyProgress.jsx +41 -0
  80. package/tabs/src/components/my_country/my_country.scss +81 -0
  81. package/tabs/src/components/publications/Publications.jsx +13 -0
  82. package/tabs/src/components/self_service/UserEdit.jsx +334 -0
  83. package/tabs/src/components/self_service/UserEdit.scss +107 -0
  84. package/tabs/src/data/apiProvider.js +153 -0
  85. package/tabs/src/data/constants.json +7 -0
  86. package/tabs/src/data/hooks/useConfiguration.js +18 -0
  87. package/tabs/src/data/icsHelper.js +38 -0
  88. package/tabs/src/data/messages.json +39 -0
  89. package/tabs/src/data/provider.js +199 -0
  90. package/tabs/src/data/selfServiceProvider.js +59 -0
  91. package/tabs/src/data/selfServiceSharepointProvider.js +68 -0
  92. package/tabs/src/data/sharepointProvider.js +729 -0
  93. package/tabs/src/data/validator.js +25 -0
  94. package/tabs/src/data/validator.test.js +9 -0
  95. package/tabs/src/index.css +16 -0
  96. package/tabs/src/index.jsx +6 -0
  97. package/tabs/src/static/images/teams-icon.svg +1 -0
  98. package/tabs/src/utils/uiHelper.js +6 -0
  99. package/templates/appPackage/aad.template.json +133 -0
  100. package/templates/appPackage/manifest.template.json +58 -0
  101. package/templates/appPackage/resources/color.png +0 -0
  102. package/templates/appPackage/resources/outline.png +0 -0
  103. package/templates/azure/config.bicep +27 -0
  104. package/templates/azure/main.bicep +20 -0
  105. package/templates/azure/provision/frontendHosting.bicep +23 -0
  106. package/templates/azure/provision/function.bicep +82 -0
  107. package/templates/azure/provision/identity.bicep +14 -0
  108. package/templates/azure/provision/simpleAuth.bicep +44 -0
  109. package/templates/azure/provision.bicep +58 -0
  110. package/templates/azure/teamsFx/function.bicep +76 -0
  111. package/templates/azure/teamsFx/simpleAuth.bicep +43 -0
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@eeacms/eionet2-dashboard",
3
+ "version": "1.4.0",
4
+ "license": "MIT",
5
+ "description": "Teams app for managing Eionet users.",
6
+ "dependencies": {
7
+ "@emotion/react": "^11.8.2",
8
+ "@emotion/styled": "^11.8.1",
9
+ "@fluentui/react-northstar": "^0.54.0",
10
+ "@microsoft/teams-js": "^1.9.0",
11
+ "@microsoft/teamsfx": "^0.5.0",
12
+ "@mui/icons-material": "^5.5.1",
13
+ "@mui/material": "^5.5.1",
14
+ "@mui/x-data-grid": "^5.6.1",
15
+ "axios": "^0.26.1",
16
+ "date-fns": "^2.29.3",
17
+ "dompurify": "^3.0.0",
18
+ "ics": "^3.1.0",
19
+ "msteams-react-base-component": "^3.1.0",
20
+ "node-sass": "^8.0.0",
21
+ "postcss-scss": "^4.0.6",
22
+ "react": "^16.14.0",
23
+ "react-dom": "^16.14.0",
24
+ "react-router-dom": "^5.1.2",
25
+ "react-scripts": "^5.0.1",
26
+ "validator": "^13.7.0"
27
+ },
28
+ "devDependencies": {
29
+ "cross-env": "^7.0.3",
30
+ "env-cmd": "^10.1.0",
31
+ "eslint": "^8.17.0",
32
+ "eslint-config-prettier": "^8.5.0",
33
+ "eslint-plugin-import": "^2.26.0",
34
+ "eslint-plugin-jest": "^26.5.3",
35
+ "eslint-plugin-jsx-a11y": "^6.5.1",
36
+ "eslint-plugin-prettier": "^4.0.0",
37
+ "eslint-plugin-react": "^7.30.0",
38
+ "eslint-plugin-react-hooks": "^4.6.0",
39
+ "jest-junit": "^14.0.0",
40
+ "prettier": "^2.6.2",
41
+ "stylelint": "^14.8.5",
42
+ "stylelint-config-idiomatic-order": "^8.1.0",
43
+ "stylelint-config-prettier": "^9.0.3",
44
+ "stylelint-prettier": "^2.0.0"
45
+ },
46
+ "scripts": {
47
+ "prepare": "npm run build",
48
+ "test": "jest",
49
+ "dev:teamsfx": "env-cmd --silent -f .env.teamsfx.local npm run start",
50
+ "start": "cross-env REACT_APP_VERSION=$npm_package_version GENERATE_SOURCEMAP=false react-scripts start",
51
+ "install:teamsfx": "npm install",
52
+ "build": "cross-env REACT_APP_VERSION=$npm_package_version GENERATE_SOURCEMAP=false react-scripts build",
53
+ "build:teamsfx": "cross-env-shell \"env-cmd -f .env.teamsfx.${TEAMS_FX_ENV} npm run build\"",
54
+ "build:teamsfx:dev": "cross-env TEAMS_FX_ENV=dev npm run build:teamsfx",
55
+ "eject": "react-scripts eject",
56
+ "stylelint": "node_modules/stylelint/bin/stylelint.js --allow-empty-input 'src/**/*.{css,scss}'",
57
+ "stylelint:fix": "yarn stylelint --fix",
58
+ "prettier": "node_modules/.bin/prettier --single-quote --check 'src/**/*.{js,jsx,json,css,scss,md}'",
59
+ "prettier:fix": "node_modules/.bin/prettier --single-quote --write 'src/**/*.{js,jsx,json,css,scss,md}'",
60
+ "lint": "node_modules/eslint/bin/eslint.js --max-warnings=0 'src/**/*.{js,jsx}'",
61
+ "lint:fix": "node_modules/eslint/bin/eslint.js --fix 'src/**/*.{js,jsx}'",
62
+ "pc": "npm run stylelint:fix & npm run prettier:fix & npm run lint:fix & npm run test"
63
+ },
64
+ "eslintConfig": {
65
+ "extends": [
66
+ "react-app",
67
+ "react-app/jest"
68
+ ]
69
+ },
70
+ "browserslist": {
71
+ "production": [
72
+ ">0.2%",
73
+ "not dead",
74
+ "not op_mini all"
75
+ ],
76
+ "development": [
77
+ "last 1 chrome version",
78
+ "last 1 firefox version",
79
+ "last 1 safari version"
80
+ ]
81
+ },
82
+ "jest": {
83
+ "transformIgnorePatterns": [
84
+ "node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)"
85
+ ]
86
+ },
87
+ "homepage": "."
88
+ }
@@ -0,0 +1,76 @@
1
+ <!--This file is used during the Teams authentication flow to assist with retrieval of the access token.-->
2
+ <!--If you're not familiar with this, do not alter or remove this file from your project.-->
3
+ <!DOCTYPE html>
4
+ <html lang="en">
5
+
6
+ <head>
7
+ <title>Login End Page</title>
8
+ <meta charset="utf-8" />
9
+ </head>
10
+
11
+ <body>
12
+ <script src="https://statics.teams.cdn.office.net/sdk/v1.6.0/js/MicrosoftTeams.min.js"
13
+ integrity="sha384-mhp2E+BLMiZLe7rDIzj19WjgXJeI32NkPvrvvZBrMi5IvWup/1NUfS5xuYN5S3VT"
14
+ crossorigin="anonymous"></script>
15
+ <script type="text/javascript">
16
+ microsoftTeams.initialize();
17
+ let hashParams = getHashParameters();
18
+
19
+ if (hashParams["error"]) {
20
+ // Authentication failed
21
+ microsoftTeams.authentication.notifyFailure(
22
+ JSON.stringify({
23
+ error: hashParams["error"],
24
+ message: JSON.stringify(hashParams),
25
+ })
26
+ );
27
+ } else if (hashParams["code"]) {
28
+ // Get the stored state parameter and compare with incoming state
29
+ let expectedState = localStorage.getItem("state");
30
+ if (expectedState !== hashParams["state"]) {
31
+ // State does not match, report error
32
+ microsoftTeams.authentication.notifyFailure(
33
+ JSON.stringify({
34
+ error: "StateDoesNotMatch",
35
+ message: JSON.stringify(hashParams),
36
+ })
37
+ );
38
+ } else {
39
+ // Success -- return code information to the parent page.
40
+ var redirectUri = location.protocol + "//" + location.host + location.pathname;
41
+ var result = JSON.stringify({
42
+ code: hashParams["code"],
43
+ codeVerifier: localStorage.getItem("codeVerifier"),
44
+ redirectUri: redirectUri,
45
+ });
46
+
47
+ microsoftTeams.authentication.notifySuccess(result);
48
+ }
49
+ } else {
50
+ // Unexpected condition: hash does not contain error or access_token parameter
51
+ microsoftTeams.authentication.notifyFailure(
52
+ JSON.stringify({
53
+ error: "UnexpectedFailure",
54
+ message: JSON.stringify(hashParams),
55
+ })
56
+ );
57
+ }
58
+
59
+ // Parse hash parameters into key-value pairs
60
+ function getHashParameters() {
61
+ let hashParams = {};
62
+ location.hash
63
+ .substr(1)
64
+ .split("&")
65
+ .forEach(function (item) {
66
+ let s = item.split("="),
67
+ k = s[0],
68
+ v = s[1] && decodeURIComponent(s[1]);
69
+ hashParams[k] = v;
70
+ });
71
+ return hashParams;
72
+ }
73
+ </script>
74
+ </body>
75
+
76
+ </html>
@@ -0,0 +1,178 @@
1
+ <!--This file is used during the Teams authentication flow to assist with retrieval of the access token.-->
2
+ <!--If you're not familiar with this, do not alter or remove this file from your project.-->
3
+ <!DOCTYPE html>
4
+ <html lang="en">
5
+
6
+ <head>
7
+ <title>Login Start Page</title>
8
+ <meta charset="utf-8" />
9
+ </head>
10
+
11
+ <body>
12
+ <script src="https://statics.teams.cdn.office.net/sdk/v1.6.0/js/MicrosoftTeams.min.js"
13
+ integrity="sha384-mhp2E+BLMiZLe7rDIzj19WjgXJeI32NkPvrvvZBrMi5IvWup/1NUfS5xuYN5S3VT"
14
+ crossorigin="anonymous"></script>
15
+ <script type="text/javascript">
16
+ microsoftTeams.initialize();
17
+
18
+ // Get the tab context, and use the information to navigate to Azure AD login page
19
+ microsoftTeams.getContext(async function (context) {
20
+ // Generate random state string and store it, so we can verify it in the callback
21
+ let state = _guid();
22
+ localStorage.setItem("state", state);
23
+ localStorage.removeItem("codeVerifier");
24
+ var currentURL = new URL(window.location);
25
+ var clientId = currentURL.searchParams.get("clientId");
26
+
27
+ var scope = currentURL.searchParams.get("scope");
28
+
29
+ var originalCode = _guid();
30
+ var codeChallenge = await pkceChallengeFromVerifier(originalCode);
31
+
32
+ localStorage.setItem("codeVerifier", originalCode);
33
+ let queryParams = {
34
+ client_id: clientId,
35
+ response_type: "code",
36
+ response_mode: "fragment",
37
+ scope: scope,
38
+ redirect_uri: window.location.origin + "/auth-end.html",
39
+ nonce: _guid(),
40
+ state: state,
41
+ login_hint: context.loginHint,
42
+ code_challenge: codeChallenge,
43
+ code_challenge_method: "S256",
44
+ };
45
+
46
+ let authorizeEndpoint = `https://login.microsoftonline.com/${context.tid
47
+ }/oauth2/v2.0/authorize?${toQueryString(queryParams)}`;
48
+ window.location.assign(authorizeEndpoint);
49
+ });
50
+
51
+ // Build query string from map of query parameter
52
+ function toQueryString(queryParams) {
53
+ let encodedQueryParams = [];
54
+ for (let key in queryParams) {
55
+ encodedQueryParams.push(key + "=" + encodeURIComponent(queryParams[key]));
56
+ }
57
+ return encodedQueryParams.join("&");
58
+ }
59
+
60
+ // Converts decimal to hex equivalent
61
+ // (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
62
+ function _decimalToHex(number) {
63
+ var hex = number.toString(16);
64
+ while (hex.length < 2) {
65
+ hex = "0" + hex;
66
+ }
67
+ return hex;
68
+ }
69
+
70
+ // Generates RFC4122 version 4 guid (128 bits)
71
+ // (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
72
+ function _guid() {
73
+ // RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
74
+ // pseudo-random numbers.
75
+ // The algorithm is as follows:
76
+ // Set the two most significant bits (bits 6 and 7) of the
77
+ // clock_seq_hi_and_reserved to zero and one, respectively.
78
+ // Set the four most significant bits (bits 12 through 15) of the
79
+ // time_hi_and_version field to the 4-bit version number from
80
+ // Section 4.1.3. Version4
81
+ // Set all the other bits to randomly (or pseudo-randomly) chosen
82
+ // values.
83
+ // UUID = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
84
+ // time-low = 4hexOctet
85
+ // time-mid = 2hexOctet
86
+ // time-high-and-version = 2hexOctet
87
+ // clock-seq-and-reserved = hexOctet:
88
+ // clock-seq-low = hexOctet
89
+ // node = 6hexOctet
90
+ // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
91
+ // y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
92
+ // y values are 8, 9, A, B
93
+ var cryptoObj = window.crypto || window.msCrypto; // for IE 11
94
+ if (cryptoObj && cryptoObj.getRandomValues) {
95
+ var buffer = new Uint8Array(16);
96
+ cryptoObj.getRandomValues(buffer);
97
+ //buffer[6] and buffer[7] represents the time_hi_and_version field. We will set the four most significant bits (4 through 7) of buffer[6] to represent decimal number 4 (UUID version number).
98
+ buffer[6] |= 0x40; //buffer[6] | 01000000 will set the 6 bit to 1.
99
+ buffer[6] &= 0x4f; //buffer[6] & 01001111 will set the 4, 5, and 7 bit to 0 such that bits 4-7 == 0100 = "4".
100
+ //buffer[8] represents the clock_seq_hi_and_reserved field. We will set the two most significant bits (6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively.
101
+ buffer[8] |= 0x80; //buffer[8] | 10000000 will set the 7 bit to 1.
102
+ buffer[8] &= 0xbf; //buffer[8] & 10111111 will set the 6 bit to 0.
103
+ return (
104
+ _decimalToHex(buffer[0]) +
105
+ _decimalToHex(buffer[1]) +
106
+ _decimalToHex(buffer[2]) +
107
+ _decimalToHex(buffer[3]) +
108
+ "-" +
109
+ _decimalToHex(buffer[4]) +
110
+ _decimalToHex(buffer[5]) +
111
+ "-" +
112
+ _decimalToHex(buffer[6]) +
113
+ _decimalToHex(buffer[7]) +
114
+ "-" +
115
+ _decimalToHex(buffer[8]) +
116
+ _decimalToHex(buffer[9]) +
117
+ "-" +
118
+ _decimalToHex(buffer[10]) +
119
+ _decimalToHex(buffer[11]) +
120
+ _decimalToHex(buffer[12]) +
121
+ _decimalToHex(buffer[13]) +
122
+ _decimalToHex(buffer[14]) +
123
+ _decimalToHex(buffer[15])
124
+ );
125
+ } else {
126
+ var guidHolder = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
127
+ var hex = "0123456789abcdef";
128
+ var r = 0;
129
+ var guidResponse = "";
130
+ for (var i = 0; i < 36; i++) {
131
+ if (guidHolder[i] !== "-" && guidHolder[i] !== "4") {
132
+ // each x and y needs to be random
133
+ r = (Math.random() * 16) | 0;
134
+ }
135
+ if (guidHolder[i] === "x") {
136
+ guidResponse += hex[r];
137
+ } else if (guidHolder[i] === "y") {
138
+ // clock-seq-and-reserved first hex is filtered and remaining hex values are random
139
+ r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
140
+ r |= 0x8; // set pos 3 to 1 as 1???
141
+ guidResponse += hex[r];
142
+ } else {
143
+ guidResponse += guidHolder[i];
144
+ }
145
+ }
146
+ return guidResponse;
147
+ }
148
+ }
149
+
150
+ // Calculate the SHA256 hash of the input text.
151
+ // Returns a promise that resolves to an ArrayBuffer
152
+ function sha256(plain) {
153
+ const encoder = new TextEncoder();
154
+ const data = encoder.encode(plain);
155
+ return window.crypto.subtle.digest("SHA-256", data);
156
+ }
157
+
158
+ // Base64-urlencodes the input string
159
+ function base64urlencode(str) {
160
+ // Convert the ArrayBuffer to string using Uint8 array to convert to what btoa accepts.
161
+ // btoa accepts chars only within ascii 0-255 and base64 encodes them.
162
+ // Then convert the base64 encoded to base64url encoded
163
+ // (replace + with -, replace / with _, trim trailing =)
164
+ return btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
165
+ .replace(/\+/g, "-")
166
+ .replace(/\//g, "_")
167
+ .replace(/=+$/, "");
168
+ }
169
+
170
+ // Return the base64-urlencoded sha256 hash for the PKCE challenge
171
+ async function pkceChallengeFromVerifier(v) {
172
+ hashed = await sha256(v);
173
+ return base64urlencode(hashed);
174
+ }
175
+ </script>
176
+ </body>
177
+
178
+ </html>
Binary file
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ <!--<!DOCTYPE html>-->
2
+ <html lang="en" style="height: 100%;">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Roboto+Flex:wght@300;400;500;700&display=swap" rel="stylesheet">
10
+ <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
11
+
12
+ <title>Microsoft Teams Tab</title>
13
+ </head>
14
+
15
+ <body style="height: 100%;">
16
+ <noscript>You need to enable JavaScript to run this app.</noscript>
17
+ <div id="root" style="min-height: 100%;"></div>
18
+ </body>
19
+
20
+ </html>
Binary file
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ // https://fluentsite.z22.web.core.windows.net/quick-start
3
+ import { Provider, teamsTheme, Loader } from '@fluentui/react-northstar';
4
+ import { HashRouter as Router, Redirect, Route } from 'react-router-dom';
5
+ import { useTeamsFx } from './lib/useTeamsFx';
6
+ import Privacy from './Privacy';
7
+ import TermsOfUse from './TermsOfUse';
8
+ import TabConfig from './TabConfig';
9
+ import Tab from './Tab';
10
+
11
+ /**
12
+ * The main app which handles the initialization and routing
13
+ * of the app.
14
+ */
15
+ export default function App() {
16
+ const { theme, loading } = useTeamsFx();
17
+ return (
18
+ <Provider theme={theme || teamsTheme}>
19
+ <Router>
20
+ <Route exact path="/">
21
+ <Redirect to="/tab" />
22
+ </Route>
23
+ {loading ? (
24
+ <Loader style={{ margin: 100 }} />
25
+ ) : (
26
+ <>
27
+ <Route exact path="/privacy" component={Privacy} />
28
+ <Route exact path="/termsofuse" component={TermsOfUse} />
29
+ <Route exact path="/tab" component={Tab} />
30
+ <Route exact path="/config" component={TabConfig} />
31
+ </>
32
+ )}
33
+ </Router>
34
+ </Provider>
35
+ );
36
+ }
@@ -0,0 +1,68 @@
1
+ import { React, useCallback, useEffect, useState } from 'react';
2
+ import { GridSeparatorIcon } from '@mui/x-data-grid';
3
+
4
+ export default function CustomColumnResizeIcon({ onWidthChanged, id }) {
5
+ const headerSelector = "[role='columnheader'][tabindex='0']";
6
+
7
+ const [initialPos, setInitialPos] = useState(null),
8
+ [resizeInfo, setResizeInfo] = useState(null),
9
+ [initialSize, setInitialSize] = useState(null);
10
+
11
+ const initial = useCallback((e) => {
12
+ let resizable = id
13
+ ? document.getElementById(id).querySelector(headerSelector)
14
+ : document.querySelector(headerSelector);
15
+
16
+ setInitialPos(e.clientX);
17
+ setInitialSize(resizable.offsetWidth);
18
+ }, []);
19
+
20
+ const resize = useCallback(
21
+ (e) => {
22
+ const columnHeader = id
23
+ ? document.getElementById(id).querySelector(headerSelector)
24
+ : document.querySelector(headerSelector),
25
+ gridElement = columnHeader.closest('.MuiDataGrid-main'),
26
+ columnHeaderStyle = columnHeader.style,
27
+ cells = gridElement.querySelectorAll(
28
+ "[role='cell'][aria-colindex='" + columnHeader.ariaColIndex + "']",
29
+ );
30
+
31
+ const newWidth = parseInt(initialSize) + parseInt(e.clientX - initialPos);
32
+ if (newWidth > 0) {
33
+ for (let i = 0; i < cells.length; i++) {
34
+ const style = cells[i].style;
35
+ style.width = style.minWidth = style.maxWidth = `${newWidth}px`;
36
+ }
37
+ columnHeaderStyle.width =
38
+ columnHeaderStyle.minWidth =
39
+ columnHeaderStyle.maxWidth =
40
+ `${newWidth}px`;
41
+
42
+ setResizeInfo({
43
+ width: newWidth,
44
+ index: columnHeader.ariaColIndex - 1,
45
+ });
46
+ }
47
+ },
48
+ [initialSize, initialPos, id],
49
+ );
50
+
51
+ useEffect(() => {
52
+ let timeout;
53
+ if (resizeInfo) {
54
+ timeout = setTimeout(() => {
55
+ onWidthChanged && onWidthChanged(resizeInfo.width, resizeInfo.index);
56
+ }, 100);
57
+ }
58
+ return () => {
59
+ clearTimeout(timeout);
60
+ };
61
+ }, [resizeInfo, onWidthChanged]);
62
+
63
+ return (
64
+ <div className="resizable" draggable onDragStart={initial} onDrag={resize}>
65
+ <GridSeparatorIcon />
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,51 @@
1
+ import { React } from 'react';
2
+ import { styled } from '@mui/material/styles';
3
+ import MuiDrawer from '@mui/material/Drawer';
4
+
5
+ export default function CustomDrawer({ drawerOptions }) {
6
+ const drawerWidth = 240;
7
+
8
+ const openedMixin = (theme) => ({
9
+ width: drawerWidth,
10
+ transition: theme.transitions.create('width', {
11
+ easing: theme.transitions.easing.sharp,
12
+ duration: theme.transitions.duration.enteringScreen,
13
+ }),
14
+ overflowX: 'hidden',
15
+ });
16
+
17
+ const closedMixin = (theme) => ({
18
+ transition: theme.transitions.create('width', {
19
+ easing: theme.transitions.easing.sharp,
20
+ duration: theme.transitions.duration.leavingScreen,
21
+ }),
22
+ overflowX: 'hidden',
23
+ width: `calc(${theme.spacing(7)} + 1px)`,
24
+ [theme.breakpoints.up('sm')]: {
25
+ width: `calc(${theme.spacing(8)} + 1px)`,
26
+ },
27
+ });
28
+
29
+ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
30
+ ({ theme, open }) => ({
31
+ width: drawerWidth,
32
+ flexShrink: 0,
33
+ whiteSpace: 'nowrap',
34
+ boxSizing: 'border-box',
35
+ ...(open && {
36
+ ...openedMixin(theme),
37
+ '& .MuiDrawer-paper': openedMixin(theme),
38
+ }),
39
+ ...(!open && {
40
+ ...closedMixin(theme),
41
+ '& .MuiDrawer-paper': closedMixin(theme),
42
+ }),
43
+ }),
44
+ );
45
+
46
+ return (
47
+ <Drawer className="drawer" variant="permanent" anchor="left" open={true}>
48
+ {drawerOptions}
49
+ </Drawer>
50
+ );
51
+ }
@@ -0,0 +1,29 @@
1
+ import { React, useState, useEffect } from 'react';
2
+ import { Box, Typography } from '@mui/material';
3
+ import { format } from 'date-fns';
4
+ import { useConfiguration } from '../data/hooks/useConfiguration';
5
+
6
+ export function EventDialogTitle({ title, event }) {
7
+ const configuration = useConfiguration(),
8
+ [longDateFormat, setLongDateFormat] = useState(undefined);
9
+
10
+ useEffect(() => {
11
+ configuration.DateFormatDashboard &&
12
+ setLongDateFormat(configuration.DateFormatDashboard + ' HH:mm');
13
+ }, [configuration]);
14
+
15
+ return (
16
+ <Box sx={{ display: 'flex', flexDirection: 'column' }}>
17
+ <Typography variant="subtitle1" color="primary" sx={{ fontWeight: 'bold' }}>
18
+ {title}
19
+ </Typography>
20
+ <Typography variant="h5">Meeting: {event.Title}</Typography>
21
+ {longDateFormat && (
22
+ <Typography variant="subtitle2">
23
+ {event.MeetingStart && format(event.MeetingStart, longDateFormat)} -{' '}
24
+ {event.MeetingEnd && format(event.MeetingEnd, longDateFormat)}
25
+ </Typography>
26
+ )}
27
+ </Box>
28
+ );
29
+ }
@@ -0,0 +1,18 @@
1
+ import { React } from 'react';
2
+ import { Box } from '@mui/material';
3
+ import DOMPurify from 'dompurify';
4
+
5
+ export function HtmlBox({ html }) {
6
+ return (
7
+ <div>
8
+ {html && (
9
+ <Box
10
+ sx={{ width: '90%' }}
11
+ dangerouslySetInnerHTML={{
12
+ __html: DOMPurify.sanitize(html),
13
+ }}
14
+ />
15
+ )}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ /**
3
+ * This component is used to display the required
4
+ * privacy statement which can be found in a link in the
5
+ * about tab.
6
+ */
7
+ class Privacy extends React.Component {
8
+ render() {
9
+ return (
10
+ <div>
11
+ <h1>Privacy Statement</h1>
12
+ </div>
13
+ );
14
+ }
15
+ }
16
+
17
+ export default Privacy;
@@ -0,0 +1,44 @@
1
+ import { React, useCallback, useEffect, useState } from 'react';
2
+ import { DataGrid } from '@mui/x-data-grid';
3
+ import CustomColumnResizeIcon from './CustomColumnResizeIcon';
4
+ import Constants from '../data/constants.json';
5
+
6
+ export default function ResizableGrid(props) {
7
+ const { columns, ...other } = props;
8
+ const [dataColumns, setDataColumns] = useState(columns);
9
+ const widthChangedHandler = useCallback(
10
+ (newWidth, columnIndex) => {
11
+ setDataColumns((columns) => {
12
+ const column = columns[columnIndex];
13
+
14
+ column.width = newWidth;
15
+ column.flex = 0;
16
+
17
+ return columns.map((c) => ({ ...c }));
18
+ });
19
+ },
20
+ [setDataColumns],
21
+ );
22
+
23
+ const ColumnResizeIcon = useCallback(() => {
24
+ return <CustomColumnResizeIcon onWidthChanged={widthChangedHandler} id={other.id} />;
25
+ }, [widthChangedHandler]);
26
+
27
+ useEffect(() => {
28
+ setDataColumns(columns);
29
+ }, [columns]);
30
+
31
+ return (
32
+ <DataGrid
33
+ sx={{ height: '98%' }}
34
+ components={{
35
+ ColumnResizeIcon: ColumnResizeIcon,
36
+ }}
37
+ columns={dataColumns}
38
+ getRowHeight={() => {
39
+ return Constants.GridRowHeight;
40
+ }}
41
+ {...other}
42
+ />
43
+ );
44
+ }