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.
- package/.fx/configs/azure.parameters.Prod_EEA.json +15 -0
- package/.fx/configs/azure.parameters.dev.json +15 -0
- package/.fx/configs/config.Prod_EEA.json +10 -0
- package/.fx/configs/config.dev.json +10 -0
- package/.fx/configs/projectSettings.json +83 -0
- package/.fx/states/state.Prod_EEA.json +47 -0
- package/.fx/states/state.dev.json +47 -0
- package/.vscode/launch.json +91 -0
- package/.vscode/settings.json +6 -0
- package/.vscode/tasks.json +63 -0
- package/CHANGELOG.md +140 -0
- package/Jenkinsfile +166 -0
- package/LICENSE.md +9 -0
- package/README.md +55 -0
- package/api/.funcignore +11 -0
- package/api/extensions.csproj +11 -0
- package/api/getGraphData/function.json +27 -0
- package/api/getGraphData/index.js +147 -0
- package/api/host.json +11 -0
- package/api/package-lock.json +1546 -0
- package/api/package.json +17 -0
- package/api/proxies.json +4 -0
- package/package.json +25 -0
- package/tabs/.env.teamsfx.Prod_EEA +11 -0
- package/tabs/.env.teamsfx.dev +11 -0
- package/tabs/.eslintrc.json +48 -0
- package/tabs/.prettierrc +7 -0
- package/tabs/.stylelintrc.json +6 -0
- package/tabs/babel.config.js +3 -0
- package/tabs/package-lock.json +15564 -0
- package/tabs/package.json +88 -0
- package/tabs/public/auth-end.html +76 -0
- package/tabs/public/auth-start.html +178 -0
- package/tabs/public/deploy.png +0 -0
- package/tabs/public/favicon.ico +0 -0
- package/tabs/public/hello.png +0 -0
- package/tabs/public/index.html +20 -0
- package/tabs/public/publish.png +0 -0
- package/tabs/src/components/App.jsx +36 -0
- package/tabs/src/components/CustomColumnResizeIcon.jsx +68 -0
- package/tabs/src/components/CustomDrawer.jsx +51 -0
- package/tabs/src/components/EventDialogTitle.jsx +29 -0
- package/tabs/src/components/HtmlBox.jsx +18 -0
- package/tabs/src/components/Privacy.jsx +17 -0
- package/tabs/src/components/ResizableGrid.jsx +44 -0
- package/tabs/src/components/Tab.jsx +477 -0
- package/tabs/src/components/Tab.scss +138 -0
- package/tabs/src/components/TabConfig.jsx +51 -0
- package/tabs/src/components/TabPanel.jsx +29 -0
- package/tabs/src/components/TermsOfUse.jsx +17 -0
- package/tabs/src/components/UnderConstruction.jsx +24 -0
- package/tabs/src/components/UserMenu.jsx +109 -0
- package/tabs/src/components/_variables.scss +10 -0
- package/tabs/src/components/activity/Activity.jsx +301 -0
- package/tabs/src/components/activity/ConsultationList.jsx +297 -0
- package/tabs/src/components/activity/EventList.jsx +463 -0
- package/tabs/src/components/activity/GroupsTags.jsx +26 -0
- package/tabs/src/components/activity/Reporting.jsx +13 -0
- package/tabs/src/components/activity/activity.scss +153 -0
- package/tabs/src/components/event_rating/EventRating.jsx +92 -0
- package/tabs/src/components/event_rating/EventRatingDialog.jsx +46 -0
- package/tabs/src/components/event_registration/Approval.jsx +80 -0
- package/tabs/src/components/event_registration/ApprovalDialog.jsx +30 -0
- package/tabs/src/components/event_registration/ApprovalList.jsx +62 -0
- package/tabs/src/components/event_registration/EventRegistration.jsx +214 -0
- package/tabs/src/components/lib/useData.js +33 -0
- package/tabs/src/components/lib/useGraph.js +39 -0
- package/tabs/src/components/lib/useTeamsFx.js +55 -0
- package/tabs/src/components/my_country/AtAGlance.jsx +151 -0
- package/tabs/src/components/my_country/CountryProgress.jsx +92 -0
- package/tabs/src/components/my_country/DataReporters.jsx +13 -0
- package/tabs/src/components/my_country/GroupView.jsx +54 -0
- package/tabs/src/components/my_country/GroupsBoard.jsx +52 -0
- package/tabs/src/components/my_country/IndicatorCard.jsx +60 -0
- package/tabs/src/components/my_country/ManagementBoard.jsx +109 -0
- package/tabs/src/components/my_country/MyCountry.jsx +186 -0
- package/tabs/src/components/my_country/ProgressGauge.jsx +125 -0
- package/tabs/src/components/my_country/ScientificCommittee.jsx +13 -0
- package/tabs/src/components/my_country/YearlyProgress.jsx +41 -0
- package/tabs/src/components/my_country/my_country.scss +81 -0
- package/tabs/src/components/publications/Publications.jsx +13 -0
- package/tabs/src/components/self_service/UserEdit.jsx +334 -0
- package/tabs/src/components/self_service/UserEdit.scss +107 -0
- package/tabs/src/data/apiProvider.js +153 -0
- package/tabs/src/data/constants.json +7 -0
- package/tabs/src/data/hooks/useConfiguration.js +18 -0
- package/tabs/src/data/icsHelper.js +38 -0
- package/tabs/src/data/messages.json +39 -0
- package/tabs/src/data/provider.js +199 -0
- package/tabs/src/data/selfServiceProvider.js +59 -0
- package/tabs/src/data/selfServiceSharepointProvider.js +68 -0
- package/tabs/src/data/sharepointProvider.js +729 -0
- package/tabs/src/data/validator.js +25 -0
- package/tabs/src/data/validator.test.js +9 -0
- package/tabs/src/index.css +16 -0
- package/tabs/src/index.jsx +6 -0
- package/tabs/src/static/images/teams-icon.svg +1 -0
- package/tabs/src/utils/uiHelper.js +6 -0
- package/templates/appPackage/aad.template.json +133 -0
- package/templates/appPackage/manifest.template.json +58 -0
- package/templates/appPackage/resources/color.png +0 -0
- package/templates/appPackage/resources/outline.png +0 -0
- package/templates/azure/config.bicep +27 -0
- package/templates/azure/main.bicep +20 -0
- package/templates/azure/provision/frontendHosting.bicep +23 -0
- package/templates/azure/provision/function.bicep +82 -0
- package/templates/azure/provision/identity.bicep +14 -0
- package/templates/azure/provision/simpleAuth.bicep +44 -0
- package/templates/azure/provision.bicep +58 -0
- package/templates/azure/teamsFx/function.bicep +76 -0
- package/templates/azure/teamsFx/simpleAuth.bicep +43 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { React, useState, useEffect } from 'react';
|
|
2
|
+
import { saveData } from '../../data/selfServiceProvider';
|
|
3
|
+
import { getGenderList } from '../../data/selfServiceSharepointProvider';
|
|
4
|
+
import { validateMandatoryField, validateName, validatePhone } from '../../data/validator';
|
|
5
|
+
import './UserEdit.scss';
|
|
6
|
+
import {
|
|
7
|
+
Box,
|
|
8
|
+
TextField,
|
|
9
|
+
Autocomplete,
|
|
10
|
+
Button,
|
|
11
|
+
FormLabel,
|
|
12
|
+
CircularProgress,
|
|
13
|
+
Chip,
|
|
14
|
+
Paper,
|
|
15
|
+
InputLabel,
|
|
16
|
+
Link,
|
|
17
|
+
Typography,
|
|
18
|
+
Divider,
|
|
19
|
+
} from '@mui/material';
|
|
20
|
+
import CheckIcon from '@mui/icons-material/Check';
|
|
21
|
+
import SaveIcon from '@mui/icons-material/Save';
|
|
22
|
+
|
|
23
|
+
export function UserEdit({ user }) {
|
|
24
|
+
const [loading, setLoading] = useState(false),
|
|
25
|
+
[success, setSuccess] = useState(false),
|
|
26
|
+
[warningVisible, setWarningVisible] = useState(false),
|
|
27
|
+
[warningText, setWarningText] = useState(''),
|
|
28
|
+
[genders, setGenders] = useState([]);
|
|
29
|
+
|
|
30
|
+
const [errors, setErrors] = useState({});
|
|
31
|
+
|
|
32
|
+
const submit = async (e) => {
|
|
33
|
+
if (!loading) {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
let tempErrors = validateForm();
|
|
36
|
+
setWarningVisible(false);
|
|
37
|
+
if (
|
|
38
|
+
!tempErrors ||
|
|
39
|
+
!Object.values(tempErrors).some((v) => {
|
|
40
|
+
return v;
|
|
41
|
+
})
|
|
42
|
+
) {
|
|
43
|
+
setSuccess(false);
|
|
44
|
+
setLoading(true);
|
|
45
|
+
let result = await saveData(user);
|
|
46
|
+
if (!result.Success) {
|
|
47
|
+
setWarningText(result.Message + '\n' + result.Error);
|
|
48
|
+
setWarningVisible(true);
|
|
49
|
+
setSuccess(false);
|
|
50
|
+
} else {
|
|
51
|
+
setWarningText('');
|
|
52
|
+
setWarningVisible(false);
|
|
53
|
+
}
|
|
54
|
+
setSuccess(true);
|
|
55
|
+
setLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
validateField = (e) => {
|
|
60
|
+
let id = e.target.id,
|
|
61
|
+
tempErrors = { ...errors };
|
|
62
|
+
|
|
63
|
+
switch (id) {
|
|
64
|
+
case 'gender':
|
|
65
|
+
tempErrors.gender = validateName(user.Gender);
|
|
66
|
+
break;
|
|
67
|
+
case 'firstName':
|
|
68
|
+
tempErrors.firstName = validateName(user.FirstName);
|
|
69
|
+
break;
|
|
70
|
+
case 'lastName':
|
|
71
|
+
tempErrors.lastName = validateName(user.LastName);
|
|
72
|
+
break;
|
|
73
|
+
case 'phone':
|
|
74
|
+
tempErrors.phone = validatePhone(user.Phone);
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
default:
|
|
78
|
+
console.log('Undefined field for validation');
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setErrors({ ...tempErrors });
|
|
83
|
+
},
|
|
84
|
+
validateForm = () => {
|
|
85
|
+
let tempErrors = { ...errors };
|
|
86
|
+
tempErrors.gender = validateMandatoryField(user.Gender);
|
|
87
|
+
tempErrors.firstName = validateName(user.FirstName);
|
|
88
|
+
tempErrors.lastName = validateName(user.LastName);
|
|
89
|
+
tempErrors.phone = validatePhone(user.Phone);
|
|
90
|
+
setErrors({ ...tempErrors });
|
|
91
|
+
return tempErrors;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
(async () => {
|
|
96
|
+
let loadedGenders = await getGenderList();
|
|
97
|
+
loadedGenders && setGenders(loadedGenders);
|
|
98
|
+
})();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="">
|
|
103
|
+
{user && (
|
|
104
|
+
<Box
|
|
105
|
+
sx={{
|
|
106
|
+
overflowY: 'scroll',
|
|
107
|
+
paddingLeft: '1.5rem',
|
|
108
|
+
paddingTop: '6rem',
|
|
109
|
+
height: '100%',
|
|
110
|
+
backgroundColor: 'suplementary.main',
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<Box
|
|
114
|
+
component="form"
|
|
115
|
+
sx={{
|
|
116
|
+
'& .MuiTextField-root': { m: 1 },
|
|
117
|
+
}}
|
|
118
|
+
autoComplete="off"
|
|
119
|
+
noValidate
|
|
120
|
+
onSubmit={(e) => {
|
|
121
|
+
submit(e);
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<Typography className="subtitle">Manage personal details</Typography>
|
|
125
|
+
<FormLabel className="note-label">
|
|
126
|
+
{user.SelfSeviceHelpdeskPersonalDetailsText}{' '}
|
|
127
|
+
</FormLabel>
|
|
128
|
+
<Box className="row-container" sx={{ backgroundColor: 'white', marginTop: '1.5rem' }}>
|
|
129
|
+
<Box className="row">
|
|
130
|
+
<Autocomplete
|
|
131
|
+
disablePortal
|
|
132
|
+
sx={{ width: '20ch', marginRight: '0.75rem' }}
|
|
133
|
+
id="combo-box-gender"
|
|
134
|
+
defaultValue={user.Gender || ''}
|
|
135
|
+
options={genders}
|
|
136
|
+
onChange={(e, value) => {
|
|
137
|
+
user.Gender = value;
|
|
138
|
+
}}
|
|
139
|
+
renderInput={(params) => (
|
|
140
|
+
<TextField
|
|
141
|
+
{...params}
|
|
142
|
+
autoComplete="off"
|
|
143
|
+
className="control"
|
|
144
|
+
label="Salutation"
|
|
145
|
+
variant="standard"
|
|
146
|
+
error={Boolean(errors?.gender)}
|
|
147
|
+
helperText={errors?.gender}
|
|
148
|
+
onBlur={validateField}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
/>
|
|
152
|
+
<TextField
|
|
153
|
+
required
|
|
154
|
+
autoComplete="off"
|
|
155
|
+
className="control"
|
|
156
|
+
id="firstName"
|
|
157
|
+
label="First name"
|
|
158
|
+
variant="standard"
|
|
159
|
+
defaultValue={user.FirstName}
|
|
160
|
+
onChange={(e) => {
|
|
161
|
+
user.FirstName = e.target.value;
|
|
162
|
+
validateField(e);
|
|
163
|
+
}}
|
|
164
|
+
inputProps={{ style: { textTransform: 'capitalize' } }}
|
|
165
|
+
error={Boolean(errors?.firstName)}
|
|
166
|
+
helperText={errors?.firstName}
|
|
167
|
+
onBlur={validateField}
|
|
168
|
+
/>
|
|
169
|
+
<TextField
|
|
170
|
+
required
|
|
171
|
+
autoComplete="off"
|
|
172
|
+
className="control"
|
|
173
|
+
id="lastName"
|
|
174
|
+
label="Last name"
|
|
175
|
+
variant="standard"
|
|
176
|
+
defaultValue={user.LastName}
|
|
177
|
+
onChange={(e) => {
|
|
178
|
+
user.LastName = e.target.value;
|
|
179
|
+
validateField(e);
|
|
180
|
+
}}
|
|
181
|
+
inputProps={{ style: { textTransform: 'capitalize' } }}
|
|
182
|
+
error={Boolean(errors?.lastName)}
|
|
183
|
+
helperText={errors?.lastName}
|
|
184
|
+
onBlur={validateField}
|
|
185
|
+
/>
|
|
186
|
+
<TextField
|
|
187
|
+
autoComplete="off"
|
|
188
|
+
className="control"
|
|
189
|
+
id="phone"
|
|
190
|
+
label="Phone"
|
|
191
|
+
variant="standard"
|
|
192
|
+
defaultValue={user.Phone}
|
|
193
|
+
onChange={(e) => {
|
|
194
|
+
user.Phone = e.target.value;
|
|
195
|
+
validateField(e);
|
|
196
|
+
}}
|
|
197
|
+
inputProps={{ maxLength: 15 }}
|
|
198
|
+
error={Boolean(errors?.phone)}
|
|
199
|
+
helperText={errors?.phone}
|
|
200
|
+
onBlur={validateField}
|
|
201
|
+
/>
|
|
202
|
+
</Box>
|
|
203
|
+
<Box className="row">
|
|
204
|
+
<TextField
|
|
205
|
+
disabled
|
|
206
|
+
required
|
|
207
|
+
autoComplete="off"
|
|
208
|
+
className="control"
|
|
209
|
+
id="lastName"
|
|
210
|
+
label="Country"
|
|
211
|
+
variant="standard"
|
|
212
|
+
defaultValue={user.Country}
|
|
213
|
+
/>
|
|
214
|
+
<TextField
|
|
215
|
+
disabled
|
|
216
|
+
required
|
|
217
|
+
autoComplete="off"
|
|
218
|
+
className="control"
|
|
219
|
+
id="email"
|
|
220
|
+
defaultValue={user.Email}
|
|
221
|
+
label="Email"
|
|
222
|
+
variant="standard"
|
|
223
|
+
/>
|
|
224
|
+
<TextField
|
|
225
|
+
disabled
|
|
226
|
+
required
|
|
227
|
+
autoComplete="off"
|
|
228
|
+
className="control"
|
|
229
|
+
id="lastName"
|
|
230
|
+
label="Organisation"
|
|
231
|
+
variant="standard"
|
|
232
|
+
defaultValue={user.Organisation}
|
|
233
|
+
/>
|
|
234
|
+
</Box>
|
|
235
|
+
</Box>
|
|
236
|
+
<Box className="row">
|
|
237
|
+
{user.Memberships && (
|
|
238
|
+
<Paper square className="paper-container" elevation={0}>
|
|
239
|
+
<InputLabel className="inputLabel">Memberships</InputLabel>
|
|
240
|
+
<Paper className="paper" elevation={0}>
|
|
241
|
+
{user.Memberships.map((data) => {
|
|
242
|
+
return (
|
|
243
|
+
<Chip
|
|
244
|
+
variant="outlined"
|
|
245
|
+
color="primary"
|
|
246
|
+
key={data}
|
|
247
|
+
className="chip"
|
|
248
|
+
label={data}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
})}
|
|
252
|
+
</Paper>
|
|
253
|
+
</Paper>
|
|
254
|
+
)}
|
|
255
|
+
{user.OtherMemberships && (
|
|
256
|
+
<Paper square className="paper-container" elevation={0}>
|
|
257
|
+
<InputLabel className="inputLabel">Other memberships</InputLabel>
|
|
258
|
+
<Paper className="paper" elevation={0}>
|
|
259
|
+
{user.OtherMemberships.map((data) => {
|
|
260
|
+
return (
|
|
261
|
+
<Chip
|
|
262
|
+
variant="outlined"
|
|
263
|
+
color="primary"
|
|
264
|
+
key={data}
|
|
265
|
+
className="chip"
|
|
266
|
+
label={data}
|
|
267
|
+
/>
|
|
268
|
+
);
|
|
269
|
+
})}
|
|
270
|
+
</Paper>
|
|
271
|
+
</Paper>
|
|
272
|
+
)}
|
|
273
|
+
{user.NFP && (
|
|
274
|
+
<Paper sx={{ marginRight: '0' }} square className="paper-container" elevation={0}>
|
|
275
|
+
<InputLabel className="inputLabel">NFP</InputLabel>
|
|
276
|
+
<Paper className="paper" elevation={0}>
|
|
277
|
+
<Chip className="chip" variant="outlined" color="primary" label={user.NFP} />
|
|
278
|
+
</Paper>
|
|
279
|
+
</Paper>
|
|
280
|
+
)}
|
|
281
|
+
</Box>
|
|
282
|
+
<div className="row">
|
|
283
|
+
<FormLabel className="note-label">
|
|
284
|
+
Note: If the email or other details needs to be changed, kindly contact{' '}
|
|
285
|
+
<Link sx={{ color: 'secondary.main' }} href="mailto:helpdesk@eea.europa.eu">
|
|
286
|
+
EEA Helpdesk
|
|
287
|
+
</Link>
|
|
288
|
+
.
|
|
289
|
+
</FormLabel>
|
|
290
|
+
</div>
|
|
291
|
+
<div className="row">
|
|
292
|
+
<Box sx={{ position: 'relative' }}>
|
|
293
|
+
<Button
|
|
294
|
+
type="submit"
|
|
295
|
+
variant="contained"
|
|
296
|
+
color="primary"
|
|
297
|
+
size="medium"
|
|
298
|
+
className="button"
|
|
299
|
+
disabled={loading}
|
|
300
|
+
endIcon={success ? <CheckIcon /> : <SaveIcon />}
|
|
301
|
+
>
|
|
302
|
+
Save changes
|
|
303
|
+
</Button>
|
|
304
|
+
{loading && (
|
|
305
|
+
<CircularProgress
|
|
306
|
+
color="primary"
|
|
307
|
+
size={24}
|
|
308
|
+
sx={{
|
|
309
|
+
position: 'absolute',
|
|
310
|
+
top: '50%',
|
|
311
|
+
left: '50%',
|
|
312
|
+
marginTop: '-12px',
|
|
313
|
+
marginLeft: '-12px',
|
|
314
|
+
}}
|
|
315
|
+
/>
|
|
316
|
+
)}
|
|
317
|
+
</Box>
|
|
318
|
+
{warningVisible && (
|
|
319
|
+
<FormLabel className="note-label warning" error>
|
|
320
|
+
{warningText}
|
|
321
|
+
</FormLabel>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
</Box>
|
|
325
|
+
<Divider sx={{ marginBottom: '1rem' }}></Divider>
|
|
326
|
+
<Box>
|
|
327
|
+
<Typography className="subtitle">Manage preferences</Typography>
|
|
328
|
+
<FormLabel className="note-label">{user.SelfSeviceHelpdeskPreferencesText} </FormLabel>
|
|
329
|
+
</Box>
|
|
330
|
+
</Box>
|
|
331
|
+
)}
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@import '../variables.scss';
|
|
2
|
+
|
|
3
|
+
.error {
|
|
4
|
+
color: red;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.row-container {
|
|
8
|
+
background-color: white;
|
|
9
|
+
width: 65%;
|
|
10
|
+
|
|
11
|
+
@media (max-width: map-get($breakpoints, lg)) {
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
width: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.row {
|
|
17
|
+
width: 100%;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.row {
|
|
22
|
+
padding: 0.2rem;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
width: 65%;
|
|
26
|
+
margin-bottom: 0.5rem;
|
|
27
|
+
|
|
28
|
+
@media (max-width: map-get($breakpoints, lg)) {
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
width: 100%;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.control {
|
|
35
|
+
margin-left: 0.75rem !important;
|
|
36
|
+
margin-right: 0.75rem !important;
|
|
37
|
+
|
|
38
|
+
@media (max-width: map-get($breakpoints, lg)) {
|
|
39
|
+
width: 100% !important;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.inputLabel {
|
|
44
|
+
margin: 0.5rem;
|
|
45
|
+
font-size: 14px !important;
|
|
46
|
+
font-weight: 500 !important;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.paper-container {
|
|
50
|
+
background-color: white;
|
|
51
|
+
width: 33%;
|
|
52
|
+
margin-right: 0.5rem;
|
|
53
|
+
margin-bottom: 0.5rem;
|
|
54
|
+
|
|
55
|
+
@media (max-width: map-get($breakpoints, lg)) {
|
|
56
|
+
width: 100% !important;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.paper {
|
|
61
|
+
margin: 8px !important;
|
|
62
|
+
width: 90%;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
justify-content: flex-start;
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
background-color: white;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.chip {
|
|
71
|
+
margin: 5px;
|
|
72
|
+
display: flex;
|
|
73
|
+
width: fit-content;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
flex-wrap: wrap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.success {
|
|
79
|
+
color: green;
|
|
80
|
+
font-weight: bold;
|
|
81
|
+
padding: 0.35rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.user-list {
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 50%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.button {
|
|
90
|
+
margin: 1rem 0 0 0.5rem;
|
|
91
|
+
position: relative;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.small-width {
|
|
95
|
+
width: 25% !important;
|
|
96
|
+
min-width: 150px !important;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.subtitle {
|
|
100
|
+
font-size: 18px !important;
|
|
101
|
+
font-weight: 600 !important;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.note-label {
|
|
105
|
+
margin-left: 0;
|
|
106
|
+
font-size: 16px !important;
|
|
107
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { TeamsUserCredential, getResourceConfiguration, ResourceType } from '@microsoft/teamsfx';
|
|
2
|
+
import * as axios from 'axios';
|
|
3
|
+
|
|
4
|
+
async function callApiFunction(command, method, options, params) {
|
|
5
|
+
let message = [];
|
|
6
|
+
|
|
7
|
+
const credential = new TeamsUserCredential();
|
|
8
|
+
const accessToken = await credential.getToken('');
|
|
9
|
+
const apiConfig = getResourceConfiguration(ResourceType.API);
|
|
10
|
+
const response = await axios.default.request({
|
|
11
|
+
method: method,
|
|
12
|
+
url: apiConfig.endpoint + '/api/' + command,
|
|
13
|
+
headers: {
|
|
14
|
+
authorization: 'Bearer ' + accessToken.token,
|
|
15
|
+
},
|
|
16
|
+
data: options,
|
|
17
|
+
params,
|
|
18
|
+
});
|
|
19
|
+
message = response.data;
|
|
20
|
+
|
|
21
|
+
return message;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function apiGet(path, credentialType = 'app') {
|
|
25
|
+
try {
|
|
26
|
+
return await callApiFunction('graphData', 'get', undefined, {
|
|
27
|
+
path: path,
|
|
28
|
+
credentialType: credentialType,
|
|
29
|
+
});
|
|
30
|
+
} catch (err) {
|
|
31
|
+
logError(err, path, null);
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function apiPost(path, data, credentialType = 'app') {
|
|
37
|
+
try {
|
|
38
|
+
return await callApiFunction('graphData', 'post', {
|
|
39
|
+
credentialType: credentialType,
|
|
40
|
+
data: data,
|
|
41
|
+
path: path,
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
logError(err, path, data);
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function apiPatch(path, data, eTag = undefined, credentialType = 'app') {
|
|
50
|
+
try {
|
|
51
|
+
return await callApiFunction('graphData', 'patch', {
|
|
52
|
+
credentialType: credentialType,
|
|
53
|
+
data: data,
|
|
54
|
+
path: path,
|
|
55
|
+
...(eTag && { eTag: eTag }),
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
logError(err, path, data);
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function apiDelete(path, credentialType = 'app') {
|
|
64
|
+
try {
|
|
65
|
+
return await callApiFunction('graphData', 'delete', {
|
|
66
|
+
credentialType: credentialType,
|
|
67
|
+
path: path,
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logError(err, path, null);
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let _userMail = undefined;
|
|
76
|
+
export async function getUserMail() {
|
|
77
|
+
if (!_userMail) {
|
|
78
|
+
const response = await apiGet('me?$select=id,displayName,mail,mobilePhone,country', 'user');
|
|
79
|
+
|
|
80
|
+
if (response.graphClientMessage) {
|
|
81
|
+
_userMail = response.graphClientMessage.mail;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return _userMail;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sharepointSiteId = process.env.REACT_APP_SHAREPOINT_SITE_ID,
|
|
88
|
+
configurationListId = process.env.REACT_APP_CONFIGURATION_LIST_ID;
|
|
89
|
+
|
|
90
|
+
let _configuration = undefined;
|
|
91
|
+
export async function getConfiguration() {
|
|
92
|
+
try {
|
|
93
|
+
if (!_configuration) {
|
|
94
|
+
const response = await apiGet(
|
|
95
|
+
'/sites/' + sharepointSiteId + '/lists/' + configurationListId + '/items?$expand=fields',
|
|
96
|
+
);
|
|
97
|
+
_configuration = {};
|
|
98
|
+
response.graphClientMessage.value.forEach(function (item) {
|
|
99
|
+
_configuration[item.fields.Title] = item.fields.Value;
|
|
100
|
+
});
|
|
101
|
+
_configuration.SharepointSiteId = sharepointSiteId;
|
|
102
|
+
}
|
|
103
|
+
return _configuration;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.log(err);
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function logError(err, apiPath, data) {
|
|
111
|
+
const spConfig = await getConfiguration(),
|
|
112
|
+
userMail = await getUserMail();
|
|
113
|
+
|
|
114
|
+
const title = err.response?.data?.error?.body;
|
|
115
|
+
|
|
116
|
+
let fields = {
|
|
117
|
+
fields: {
|
|
118
|
+
ApplicationName: 'Eionet2-Dashboard',
|
|
119
|
+
ApiPath: apiPath,
|
|
120
|
+
ApiData: JSON.stringify(data),
|
|
121
|
+
Title: title ? title : err,
|
|
122
|
+
UserMail: userMail,
|
|
123
|
+
Timestamp: new Date(),
|
|
124
|
+
Logtype: 'Error',
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
let graphURL =
|
|
129
|
+
'/sites/' + spConfig.SharepointSiteId + '/lists/' + spConfig.LoggingListId + '/items';
|
|
130
|
+
await apiPost(graphURL, fields);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function logInfo(message, apiPath, data, action) {
|
|
134
|
+
const spConfig = await getConfiguration(),
|
|
135
|
+
userMail = await getUserMail();
|
|
136
|
+
|
|
137
|
+
let fields = {
|
|
138
|
+
fields: {
|
|
139
|
+
ApplicationName: 'Eionet2-Dashboard',
|
|
140
|
+
ApiPath: apiPath,
|
|
141
|
+
ApiData: JSON.stringify(data),
|
|
142
|
+
Title: message,
|
|
143
|
+
UserMail: userMail,
|
|
144
|
+
Timestamp: new Date(),
|
|
145
|
+
Logtype: 'Info',
|
|
146
|
+
Action: action,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
let graphURL =
|
|
151
|
+
'/sites/' + spConfig.SharepointSiteId + '/lists/' + spConfig.LoggingListId + '/items';
|
|
152
|
+
await apiPost(graphURL, fields);
|
|
153
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { getConfiguration } from '../apiProvider';
|
|
3
|
+
|
|
4
|
+
export function useConfiguration() {
|
|
5
|
+
const [configuration, setConfiguration] = useState({});
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
async function fetchData() {
|
|
9
|
+
let loadedConfiguration = await getConfiguration();
|
|
10
|
+
if (loadedConfiguration) {
|
|
11
|
+
setConfiguration(loadedConfiguration);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
fetchData();
|
|
15
|
+
}, [getConfiguration]);
|
|
16
|
+
|
|
17
|
+
return configuration;
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createEvent } from 'ics';
|
|
2
|
+
import { differenceInMinutes } from 'date-fns';
|
|
3
|
+
|
|
4
|
+
export function createIcs(meeting) {
|
|
5
|
+
let result = undefined,
|
|
6
|
+
duration = undefined;
|
|
7
|
+
const meetingStart = new Date(meeting.MeetingStart);
|
|
8
|
+
const durationInMinutes = differenceInMinutes(new Date(meeting.MeetingEnd), meetingStart);
|
|
9
|
+
if (durationInMinutes >= 60) {
|
|
10
|
+
duration = {
|
|
11
|
+
hours: Math.floor(durationInMinutes / 60),
|
|
12
|
+
minutes: durationInMinutes % 60,
|
|
13
|
+
};
|
|
14
|
+
} else {
|
|
15
|
+
duration = {
|
|
16
|
+
minutes: durationInMinutes,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const event = {
|
|
20
|
+
start: [
|
|
21
|
+
meetingStart.getFullYear(),
|
|
22
|
+
meetingStart.getMonth(),
|
|
23
|
+
meetingStart.getDate(),
|
|
24
|
+
meetingStart.getHours(),
|
|
25
|
+
meetingStart.getMinutes(),
|
|
26
|
+
],
|
|
27
|
+
duration: duration,
|
|
28
|
+
title: meeting.Title,
|
|
29
|
+
...(meeting.MeetingLink && { url: meeting.MeetingLink }),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
createEvent(event, (error, value) => {
|
|
33
|
+
const blob = new Blob([value], { type: 'text/plain;charset=utf-8' });
|
|
34
|
+
result = blob;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"UserInvite": {
|
|
3
|
+
"UserAlreadyRegistered": "User with this email already registered.",
|
|
4
|
+
"InvalidEmail": "Invalid email. Please correct the email address!",
|
|
5
|
+
"EEAUserError": "Users with EEA email address cannot be invited as guests.",
|
|
6
|
+
"Errors": {
|
|
7
|
+
"Invitation": "Could not create the user invitation.",
|
|
8
|
+
"ADUserCreation": "Could not update the user contact data.",
|
|
9
|
+
"JoiningTeam": "The user could not be added to the Eionet teams. Please use the Manage user tab to update the user Membership(s)",
|
|
10
|
+
"TagsCreation": "The tags could not be updated. Please use the Manage user tab to update the user Membership(s)",
|
|
11
|
+
"SharepointUser": "User details could not be updated in the SharePoint Users list.",
|
|
12
|
+
"Mail": "An error has occured during the sending invitation email.",
|
|
13
|
+
"Error": "An error has occured during the invitation process."
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"UserList": {
|
|
17
|
+
"MissingADUser": "The user account cannot be updated (account is not present in the Azure tenant).",
|
|
18
|
+
"DeleteUser": "Are you sure you want to remove the user #name# from the Eionet directory?",
|
|
19
|
+
"DeleteUserMemberships": "The user is also a member of the following groups at EEA:"
|
|
20
|
+
},
|
|
21
|
+
"UserEdit": {
|
|
22
|
+
"MissingMembership": "At least one the following fields must be selected Eionet groups, Other memberships or NFP.",
|
|
23
|
+
"Errors": {
|
|
24
|
+
"ADUserCreation": "Could not update the user contact data.",
|
|
25
|
+
"Mail": "An error has occured during the sending inviation email.",
|
|
26
|
+
"Invitation": "Could not create the user invitation.",
|
|
27
|
+
"ADUser": "Could not update the user contact data.",
|
|
28
|
+
"JoiningTeam": "The user could not be added to the Eionet teams. Please use the Manage user tab to update the user Membership(s)",
|
|
29
|
+
"TagsCreation": "The tags could not be updated. Please use the Manage user tab to update the user Membership(s)",
|
|
30
|
+
"SharepointUser": "User details could not be updated in the SharePoint Users list.",
|
|
31
|
+
"Error": "An error has occured when saving user information."
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"UserDelete": {
|
|
35
|
+
"Errors": {
|
|
36
|
+
"ADUser": "Could not update the user information."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|