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,729 @@
1
+ import { apiGet, apiPost, apiPatch, getConfiguration, apiDelete, logError } from './apiProvider';
2
+ import { format, differenceInDays, addDays } from 'date-fns';
3
+ import { sendEmail } from './provider';
4
+ import { createIcs } from './icsHelper';
5
+
6
+ function wrapError(err, message) {
7
+ return {
8
+ Message: message,
9
+ Error: err,
10
+ Success: false,
11
+ };
12
+ }
13
+
14
+ const MEETING_TITLE_PLACEHOLDER = '{MeetingTitle}',
15
+ MEETING_JOIN_URL_PLACEHOLDER = '{MeetingJoinUrl}';
16
+
17
+ export async function getOrganisationList(country) {
18
+ const config = await getConfiguration();
19
+ try {
20
+ let path =
21
+ '/sites/' +
22
+ config.SharepointSiteId +
23
+ '/lists/' +
24
+ config.OrganisationListId +
25
+ '/items?$expand=fields&$top=999';
26
+ if (country) {
27
+ path += "&$filter=fields/Country eq '" + country + "'";
28
+ }
29
+ const response = await apiGet(path);
30
+ return response.graphClientMessage.value.map(function (organisation) {
31
+ return {
32
+ header: organisation.fields.Title,
33
+ content: organisation.id,
34
+ };
35
+ });
36
+ } catch (err) {
37
+ console.log(err);
38
+ return [];
39
+ }
40
+ }
41
+
42
+ let mappingsList = undefined;
43
+ export async function getMappingsList() {
44
+ const config = await getConfiguration();
45
+ try {
46
+ if (!mappingsList) {
47
+ const response = await apiGet(
48
+ '/sites/' +
49
+ config.SharepointSiteId +
50
+ '/lists/' +
51
+ config.MappingListId +
52
+ '/items?$expand=fields',
53
+ );
54
+ mappingsList = response.graphClientMessage.value.map(function (mapping) {
55
+ return {
56
+ TeamURL: mapping.fields.TeamURL,
57
+ O365Group: mapping.fields.O365group,
58
+ O365GroupId: mapping.fields.O365GroupId,
59
+ Membership: mapping.fields.Membership,
60
+ Tag: mapping.fields.Tag,
61
+ OtherMembership: mapping.fields.OtherMembership,
62
+ ManagementBoard: mapping.fields.ManagementBoard,
63
+ };
64
+ });
65
+ }
66
+ return mappingsList;
67
+ } catch (err) {
68
+ console.log(err);
69
+ }
70
+ }
71
+
72
+ export async function getCountries() {
73
+ const config = await getConfiguration();
74
+ try {
75
+ const response = await apiGet(
76
+ '/sites/' + config.SharepointSiteId + '/lists/' + config.UserListId + '/columns',
77
+ );
78
+ const columns = response.graphClientMessage.value;
79
+
80
+ const countryColumn = columns.find((column) => column.name === 'Country');
81
+ if (countryColumn && countryColumn.choice) {
82
+ return countryColumn.choice.choices;
83
+ }
84
+
85
+ return [];
86
+ } catch (err) {
87
+ return wrapError(err, 'Error loading countries');
88
+ }
89
+ }
90
+
91
+ export async function getAvailableGroups() {
92
+ const config = await getConfiguration();
93
+ try {
94
+ const response = await apiGet(
95
+ '/sites/' + config.SharepointSiteId + '/lists/' + config.UserListId + '/columns',
96
+ );
97
+ const columns = response.graphClientMessage.value;
98
+
99
+ let result = [];
100
+
101
+ const column = columns.find((column) => column.name === 'Membership');
102
+ column && column.choice && (result = result.concat(column.choice.choices));
103
+
104
+ return result;
105
+ } catch (err) {
106
+ return wrapError(err, 'Error loading groups');
107
+ }
108
+ }
109
+
110
+ export async function getSPUserByMail(email) {
111
+ const config = await getConfiguration();
112
+ try {
113
+ const path =
114
+ '/sites/' +
115
+ config.SharepointSiteId +
116
+ '/lists/' +
117
+ config.UserListId +
118
+ "/items?$filter=fields/Email eq '" +
119
+ email +
120
+ "'&$expand=fields",
121
+ response = await apiGet(path),
122
+ profile = response.graphClientMessage;
123
+ if (profile.value && profile.value.length) {
124
+ return profile.value[0];
125
+ }
126
+ return undefined;
127
+ } catch (err) {
128
+ console.log(err);
129
+ }
130
+ }
131
+
132
+ export async function getConsultations(consultationType, fromDate, userCountry) {
133
+ const config = await getConfiguration();
134
+ try {
135
+ let path =
136
+ '/sites/' +
137
+ config.SharepointSiteId +
138
+ '/lists/' +
139
+ config.ConsultationListId +
140
+ '/items?$expand=fields&$top=999';
141
+
142
+ if (consultationType) {
143
+ path += "&$filter=fields/ConsultationType eq '";
144
+ path += consultationType + "'";
145
+ }
146
+
147
+ if (fromDate) {
148
+ path += "&$filter=fields/Startdate ge '";
149
+ path += format(new Date(fromDate), 'yyyy-MM-dd') + "'";
150
+ }
151
+
152
+ const response = await apiGet(path),
153
+ consultations = await response.graphClientMessage;
154
+
155
+ const currentDate = new Date(new Date().toDateString());
156
+
157
+ return consultations.value.map(function (consultation) {
158
+ const respondants = consultation.fields.Respondants || [],
159
+ hasUserCountryResponded = userCountry && respondants.includes(userCountry);
160
+ return {
161
+ id: consultation.fields.id,
162
+
163
+ Title: consultation.fields.Title,
164
+ ConsultationType: consultation.fields.ConsultationType,
165
+ Description: consultation.fields.Description,
166
+
167
+ Startdate: new Date(consultation.fields.Startdate),
168
+ Closed: new Date(consultation.fields.Closed),
169
+ Deadline: new Date(consultation.fields.Deadline),
170
+ Year: new Date(consultation.fields.Startdate).getFullYear(),
171
+ DaysLeft: differenceInDays(new Date(consultation.fields.Closed), currentDate),
172
+ DaysFinalised: differenceInDays(new Date(consultation.fields.Deadline), currentDate),
173
+
174
+ Linktofolder: consultation.fields.LinktoFolder,
175
+ Respondants: respondants,
176
+ HasUserCountryResponded: hasUserCountryResponded,
177
+ Countries: consultation.fields.Countries,
178
+
179
+ ConsulationmanagerLookupId: consultation.fields.ConsulationmanagerLookupId,
180
+ EionetGroups: consultation.fields.EionetGroups,
181
+ LinkToResults: consultation.fields.LinkToResults,
182
+ ItemLink:
183
+ config.ConsultationListUrl + '?FilterField1=ID&FilterValue1=' + consultation.fields.id,
184
+ };
185
+ });
186
+ } catch (err) {
187
+ console.log(err);
188
+ }
189
+ }
190
+
191
+ export async function getMeetings(fromDate, country, userInfo) {
192
+ const config = await getConfiguration();
193
+ try {
194
+ let path =
195
+ '/sites/' +
196
+ config.SharepointSiteId +
197
+ '/lists/' +
198
+ config.MeetingListId +
199
+ '/items?$expand=fields&$top=999';
200
+
201
+ if (fromDate) {
202
+ path += "&$filter=fields/Meetingstart ge '";
203
+ path += format(new Date(fromDate), 'yyyy-MM-dd') + "'";
204
+ }
205
+
206
+ const response = await apiGet(path),
207
+ meetings = response.graphClientMessage.value,
208
+ allParticipants = await getParticipants(undefined, country);
209
+
210
+ return await Promise.all(
211
+ meetings.map(async (meeting) => {
212
+ const meetingId = meeting.fields.id,
213
+ participants = allParticipants.filter((p) => p.MeetingId == meetingId),
214
+ participantsCount = participants.filter((p) => {
215
+ return p.Participated;
216
+ }).length,
217
+ registerCount = participants.filter((p) => {
218
+ return p.Registered;
219
+ }).length;
220
+
221
+ const currentDate = new Date(),
222
+ meetingStart = new Date(meeting.fields.Meetingstart),
223
+ meetingEnd = new Date(meeting.fields.Meetingend),
224
+ meetingTitle = meeting.fields.Title,
225
+ isPast = meetingEnd < currentDate;
226
+
227
+ const countryFilterSuffix = country
228
+ ? '&FilterField3=Countries&FilterValue3=' + country
229
+ : '';
230
+ const meetingFilterSuffix =
231
+ '?FilterField1=Meetingtitle&FilterType1=Lookup&FilterValue1=' + meetingTitle;
232
+
233
+ let currentParticipant =
234
+ participants && participants.length && participants.find((p) => p.Email == userInfo.mail);
235
+
236
+ const allowVote =
237
+ userInfo.country &&
238
+ currentParticipant &&
239
+ !currentParticipant.Voted &&
240
+ (currentParticipant.Registered || currentParticipant.Participated) &&
241
+ meetingStart <= currentDate &&
242
+ currentDate <= addDays(meetingStart, config.NoOfDaysForRating);
243
+
244
+ return {
245
+ id: meetingId,
246
+
247
+ Title: meetingTitle,
248
+ MeetingLink: meeting.fields.MeetingLink,
249
+ MeetingRegistrationLink: meeting.fields.MeetingRegistrationLink,
250
+ Group: meeting.fields.Group,
251
+
252
+ MeetingStart: new Date(meeting.fields.Meetingstart),
253
+ MeetingEnd: new Date(meeting.fields.Meetingend),
254
+ MeetingType: meeting.fields.MeetingType,
255
+
256
+ Year: meetingStart.getFullYear(),
257
+ Linktofolder: meeting.fields.Linktofolder,
258
+
259
+ NoOfParticipants: country ? participantsCount : meeting.fields.NoOfParticipants,
260
+ ParticipantsUrl:
261
+ config.MeetingParticipantsListUrl +
262
+ meetingFilterSuffix +
263
+ '&FilterField2=Participated&FilterValue2=1&FilterType2=Boolean' +
264
+ countryFilterSuffix,
265
+ NoOfRegistered: country ? registerCount : meeting.fields.NoOfRegistered,
266
+ RegisteredUrl:
267
+ config.MeetingParticipantsListUrl +
268
+ meetingFilterSuffix +
269
+ '&FilterField2=Registered&FilterValue2=1&FilterType2=Boolean' +
270
+ countryFilterSuffix,
271
+ Participants: participants,
272
+
273
+ IsCurrent: meetingStart <= new Date() && meetingEnd >= new Date(),
274
+ IsUpcoming: meetingStart > new Date(),
275
+ IsPast: isPast,
276
+
277
+ IsOnline: meeting.fields.MeetingType && meeting.fields.MeetingType == 'Online',
278
+ IsOffline: meeting.fields.MeetingType && meeting.fields.MeetingType != 'Online',
279
+
280
+ CustomMeetingRequest: meeting.fields.CustomMeetingRequests,
281
+
282
+ HasRegistered: !!(currentParticipant && currentParticipant.Registered),
283
+ HasVoted: !!(currentParticipant && currentParticipant.Voted),
284
+ AllowVote: allowVote,
285
+ };
286
+ }),
287
+ );
288
+ } catch (err) {
289
+ console.log(err);
290
+ }
291
+ }
292
+
293
+ export async function getParticipants(meetingId, country) {
294
+ if (!meetingId && !country) {
295
+ return [];
296
+ }
297
+
298
+ let hasFilter = false;
299
+ const config = await getConfiguration();
300
+ try {
301
+ let path =
302
+ '/sites/' +
303
+ config.SharepointSiteId +
304
+ '/lists/' +
305
+ config.MeetingParticipantsListId +
306
+ '/items?$expand=fields&$top=999';
307
+
308
+ if (meetingId) {
309
+ path += '&$filter=fields/MeetingtitleLookupId eq ';
310
+ path += meetingId;
311
+ hasFilter = true;
312
+ }
313
+
314
+ if (country) {
315
+ path += hasFilter ? 'and ' : '&$filter=';
316
+ path += "fields/Countries eq '";
317
+ path += country;
318
+ path += "'";
319
+ }
320
+
321
+ let result = [];
322
+ while (path) {
323
+ const response = await apiGet(path),
324
+ participants = response.graphClientMessage;
325
+
326
+ if (participants && participants.value) {
327
+ participants.value.forEach((p) => {
328
+ result.push({
329
+ id: p.fields.id,
330
+ MeetingId: p.fields.MeetingtitleLookupId,
331
+ ParticipantName: p.fields.Participantname,
332
+ Email: p.fields.EMail,
333
+ Country: p.fields.Countries,
334
+ Registered: p.fields.Registered,
335
+ Participated: p.fields.Participated,
336
+ PhysicalParticipation: p.fields.PhysicalParticipation,
337
+ EEAReimbursementRequested: p.fields.EEAReimbursementRequested,
338
+ CustomMeetingRequest: p.fields.CustomMeetingRequest,
339
+ NFPApproved: p.fields.NFPApproved,
340
+ Voted: p.fields.Voted,
341
+ });
342
+ });
343
+ }
344
+
345
+ path = participants['@odata.nextLink'];
346
+ }
347
+
348
+ return result;
349
+ } catch (err) {
350
+ console.log(err);
351
+ }
352
+ }
353
+
354
+ export async function getCurrentParticipant(event, userInfo) {
355
+ event.Participants = await getParticipants(event.id, userInfo.country);
356
+ let participant =
357
+ event.Participants &&
358
+ event.Participants.length &&
359
+ event.Participants.find((p) => p.Email == userInfo.mail);
360
+
361
+ if (!participant) {
362
+ participant = {
363
+ MeetingId: event.id,
364
+ ParticipantName: userInfo.givenName + ' ' + userInfo.surname,
365
+ Email: userInfo.mail,
366
+ Country: userInfo.country,
367
+ Registered: false,
368
+ Participated: false,
369
+ PhysicalParticipation: false,
370
+ EEAReimbursementRequested: false,
371
+ };
372
+ }
373
+ return participant;
374
+ }
375
+
376
+ export async function getInvitedUsers(country) {
377
+ const config = await getConfiguration();
378
+ try {
379
+ let path =
380
+ '/sites/' +
381
+ config.SharepointSiteId +
382
+ '/lists/' +
383
+ config.UserListId +
384
+ '/items?$expand=fields&$top=999';
385
+ if (country) {
386
+ path += "&$filter=fields/Country eq '" + country + "'";
387
+ }
388
+
389
+ let result = [];
390
+ const organisations = await getOrganisationList();
391
+
392
+ while (path) {
393
+ const response = await apiGet(path),
394
+ users = await response.graphClientMessage;
395
+
396
+ users.value.forEach(function (user) {
397
+ let organisation = organisations.find(
398
+ (o) => o.content === user.fields.OrganisationLookupId,
399
+ );
400
+
401
+ //concatenate memberships, otherMemberships and NFP in one field to display in grid
402
+ let memberships = (user.fields.Membership || []).concat(user.fields.OtherMemberships || []);
403
+ user.fields.NFP && memberships.push(user.fields.NFP);
404
+
405
+ result.push({
406
+ Title: user.fields.Title,
407
+ Email: user.fields.Email,
408
+ Membership: user.fields.Membership,
409
+ AllMemberships: memberships,
410
+ OtherMemberships: user.fields.OtherMemberships,
411
+ OtherMembershipsString:
412
+ user.fields.OtherMemberships && user.fields.OtherMemberships.toString(),
413
+ Country: user.fields.Country,
414
+ OrganisationLookupId: user.fields.OrganisationLookupId,
415
+ Organisation: organisation?.header,
416
+ ADUserId: user.fields.ADUserId,
417
+ NFP: user.fields.NFP,
418
+ SignedIn: user.fields.SignedIn,
419
+ id: user.fields.id,
420
+ });
421
+ });
422
+
423
+ path = users['@odata.nextLink'];
424
+ }
425
+ return result;
426
+ } catch (err) {
427
+ console.log(err);
428
+ return [];
429
+ }
430
+ }
431
+
432
+ export function getGroups(users) {
433
+ let groups = [];
434
+
435
+ if (users && users.length) {
436
+ users.forEach((user) => {
437
+ groups = groups.concat(user.Membership);
438
+ });
439
+ }
440
+ return [...new Set(groups)];
441
+ }
442
+
443
+ export async function postParticipant(participant, event) {
444
+ const config = await getConfiguration(),
445
+ graphURL =
446
+ '/sites/' + config.SharepointSiteId + '/lists/' + config.MeetingParticipantsListId + '/items';
447
+
448
+ const participantData = {
449
+ fields: {
450
+ MeetingtitleLookupId: participant.MeetingId,
451
+ Participantname: participant.ParticipantName,
452
+ EMail: participant.Email,
453
+ Countries: participant.Country,
454
+ Registered: participant.Registered,
455
+ Participated: participant.Participated,
456
+ RegistrationDate: participant.RegistrationDate,
457
+ PhysicalParticipation: participant.PhysicalParticipation,
458
+ EEAReimbursementRequested: participant.EEAReimbursementRequested,
459
+ CustomMeetingRequest: participant.CustomMeetingRequest,
460
+ },
461
+ };
462
+
463
+ try {
464
+ const response = await apiPost(graphURL, participantData);
465
+
466
+ await sendEmail(
467
+ getNotificationSubject(config, event, false),
468
+ getNotificationBody(config, event, false),
469
+ [participant.Email],
470
+ event.IsOnline ? createIcs(event) : undefined,
471
+ );
472
+ await sentNFPNotification(participant, event);
473
+ return response?.graphClientMessage;
474
+ } catch (err) {
475
+ return false;
476
+ }
477
+ }
478
+
479
+ export async function patchParticipants(participants, event) {
480
+ for (const participant of participants) {
481
+ participant.NFPApprovalChanged && (await patchParticipant(participant, event, true));
482
+ participant.NFPApprovalChanged = false;
483
+ }
484
+ }
485
+
486
+ export async function patchParticipant(participant, event, approvalChanged) {
487
+ const config = await getConfiguration(),
488
+ graphURL =
489
+ '/sites/' +
490
+ config.SharepointSiteId +
491
+ '/lists/' +
492
+ config.MeetingParticipantsListId +
493
+ '/items/' +
494
+ participant.id;
495
+
496
+ const participantData = {
497
+ fields: {
498
+ Registered: participant.Registered,
499
+ Participated: participant.Participated,
500
+ PhysicalParticipation: participant.PhysicalParticipation,
501
+ EEAReimbursementRequested: participant.EEAReimbursementRequested,
502
+ CustomMeetingRequest: participant.CustomMeetingRequest,
503
+ NFPApproved: participant.NFPApproved,
504
+ },
505
+ };
506
+
507
+ try {
508
+ await apiPatch(graphURL, participantData);
509
+
510
+ if (approvalChanged) {
511
+ if (participant.NFPApproved && participant.NFPApproved != 'No value') {
512
+ const isApproved = participant.NFPApproved == 'Approved',
513
+ bodyPropperty =
514
+ 'Reg' +
515
+ (event.MeetingType == 'Online' ? 'Online' : 'Offline') +
516
+ (isApproved ? 'NFPAccepts' : 'NFPDeclines'),
517
+ attachment = isApproved ? createIcs(event) : undefined;
518
+ await sendEmail(
519
+ getNotificationSubject(config, event, false),
520
+ replacePlaceholders(config[bodyPropperty], event),
521
+ [participant.Email],
522
+ attachment,
523
+ );
524
+ }
525
+ } else {
526
+ await sendEmail(
527
+ getNotificationSubject(config, event, false),
528
+ getNotificationBody(config, event, false),
529
+ [participant.Email],
530
+ );
531
+ await sentNFPNotification(participant, event);
532
+ }
533
+
534
+ return true;
535
+ } catch (err) {
536
+ await logError(err);
537
+ return false;
538
+ }
539
+ }
540
+
541
+ function getNotificationSubject(config, event, forNFP) {
542
+ const propName = event.MeetingType == 'Online' ? 'Online' : 'Offline';
543
+ let emailSubjectProperty = 'Reg' + propName + 'EmailSubject';
544
+
545
+ forNFP ? (emailSubjectProperty += 'NFP') : (emailSubjectProperty += 'User');
546
+ let subject = config[emailSubjectProperty];
547
+ return subject && subject.replaceAll(MEETING_TITLE_PLACEHOLDER, event.Title);
548
+ }
549
+
550
+ function getNotificationBody(config, event, forNFP) {
551
+ const propName = event.MeetingType == 'Online' ? 'Online' : 'Offline';
552
+ let emailBodyProperty = 'Reg' + propName + 'EmailBody';
553
+
554
+ forNFP ? (emailBodyProperty += 'NFP') : (emailBodyProperty += 'User');
555
+ return replacePlaceholders(config[emailBodyProperty], event);
556
+ }
557
+
558
+ function replacePlaceholders(property, event) {
559
+ if (property) {
560
+ property = property.replaceAll(MEETING_TITLE_PLACEHOLDER, event.Title);
561
+ property = property.replaceAll(MEETING_JOIN_URL_PLACEHOLDER, event.MeetingLink);
562
+ }
563
+
564
+ //event.MeetingJoinContent && (property += event.MeetingJoinContent);
565
+ return property;
566
+ }
567
+
568
+ async function sentNFPNotification(participant, event) {
569
+ if (participant && participant.Country) {
570
+ const config = await getConfiguration(),
571
+ users = await getInvitedUsers(participant.Country);
572
+
573
+ const nfpUsers = users.filter((u) => !!u.NFP);
574
+ if (nfpUsers && nfpUsers.length) {
575
+ await sendEmail(
576
+ getNotificationSubject(config, event, true),
577
+ getNotificationBody(config, event, true),
578
+ nfpUsers.map((u) => u.Email) || [],
579
+ );
580
+ } else {
581
+ await logError(
582
+ 'No NFP found to notify for the user with email ' + participant.Email,
583
+ '',
584
+ participant,
585
+ );
586
+ }
587
+ } else {
588
+ await logError(
589
+ 'The NFP couldn’t be notified for the user with email ' +
590
+ participant.Email +
591
+ ' because the user does not have a country specified.',
592
+ '',
593
+ participant,
594
+ );
595
+ }
596
+ }
597
+
598
+ export async function deleteParticipant(participant) {
599
+ const config = await getConfiguration(),
600
+ graphURL =
601
+ '/sites/' +
602
+ config.SharepointSiteId +
603
+ '/lists/' +
604
+ config.MeetingParticipantsListId +
605
+ '/items/' +
606
+ participant.id;
607
+ try {
608
+ const response = await apiDelete(graphURL);
609
+ return response?.graphClientMessage;
610
+ } catch (err) {
611
+ return false;
612
+ }
613
+ }
614
+
615
+ const meetingManagers = {};
616
+ export async function getADUserId(lookupId) {
617
+ if (lookupId) {
618
+ if (!meetingManagers[lookupId]) {
619
+ const config = await getConfiguration();
620
+ try {
621
+ let path =
622
+ '/sites/' + config.SharepointSiteId + '/lists/User Information List/items/' + lookupId;
623
+
624
+ const response = await apiGet(path);
625
+ if (response.graphClientMessage) {
626
+ const userInfo = response.graphClientMessage.fields;
627
+
628
+ const adResponse = await apiGet('/users/' + userInfo.EMail);
629
+ const userId = adResponse?.graphClientMessage?.id;
630
+ if (userId) {
631
+ meetingManagers[lookupId] = userId;
632
+ }
633
+ }
634
+
635
+ return undefined;
636
+ } catch (error) {
637
+ console.log(error);
638
+ return undefined;
639
+ }
640
+ }
641
+ }
642
+
643
+ return meetingManagers[lookupId];
644
+ }
645
+
646
+ async function loadRating(eventId) {
647
+ const config = await getConfiguration(),
648
+ ratingGraphURL =
649
+ '/sites/' + config.SharepointSiteId + '/lists/' + config.MeetingRatingListId + '/items',
650
+ response = await apiGet(
651
+ ratingGraphURL + '?$expand=fields&$filter=fields/EventLookupId eq ' + eventId,
652
+ );
653
+
654
+ if (response.graphClientMessage && response.graphClientMessage.value.length) {
655
+ return response.graphClientMessage.value[0];
656
+ }
657
+ return undefined;
658
+ }
659
+
660
+ function buildRatingData(rating, value) {
661
+ return {
662
+ fields: {
663
+ Responses: rating.fields.Responses + 1,
664
+ Rating: rating.fields.Rating + value,
665
+ },
666
+ };
667
+ }
668
+
669
+ export async function postRating(event, participant, value) {
670
+ const config = await getConfiguration(),
671
+ ratingGraphURL =
672
+ '/sites/' + config.SharepointSiteId + '/lists/' + config.MeetingRatingListId + '/items',
673
+ participantGraphURL =
674
+ '/sites/' +
675
+ config.SharepointSiteId +
676
+ '/lists/' +
677
+ config.MeetingParticipantsListId +
678
+ '/items/' +
679
+ participant.id;
680
+
681
+ let success = false,
682
+ existingRating = await loadRating(event.id);
683
+ if (existingRating) {
684
+ let ratingData = buildRatingData(existingRating, value);
685
+ let retryPatch = true;
686
+ while (retryPatch) {
687
+ try {
688
+ //send eTag to make sure record was not modified. Reload record if modified.
689
+ await apiPatch(ratingGraphURL + '/' + existingRating.id, ratingData, existingRating.eTag);
690
+ retryPatch = false;
691
+ success = true;
692
+ } catch (err) {
693
+ retryPatch = err.response?.status == 412;
694
+ if (retryPatch) {
695
+ existingRating = await loadRating(event.id);
696
+ ratingData = buildRatingData(existingRating, value);
697
+ }
698
+ }
699
+ }
700
+ } else {
701
+ const ratingData = {
702
+ fields: {
703
+ EventLookupId: event.id,
704
+ Responses: 1,
705
+ Rating: value,
706
+ },
707
+ };
708
+ try {
709
+ await apiPost(ratingGraphURL, ratingData);
710
+ success = true;
711
+ } catch (err) {
712
+ //await logError(err);
713
+ success = false;
714
+ }
715
+ }
716
+
717
+ if (success) {
718
+ try {
719
+ await apiPatch(participantGraphURL, {
720
+ fields: {
721
+ Voted: true,
722
+ },
723
+ });
724
+ } catch (err) {
725
+ return false;
726
+ }
727
+ }
728
+ return success;
729
+ }