@plusscommunities/pluss-circles-web-groups 1.0.17-beta.0 → 1.0.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-circles-web-groups",
3
- "version": "1.0.17-beta.0",
3
+ "version": "1.0.18",
4
4
  "description": "Extension package to enable circles on Pluss Communities Platform",
5
5
  "main": "dist/index.cjs.js",
6
6
  "scripts": {
@@ -35,7 +35,10 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@babel/runtime": "^7.14.0",
38
- "@plusscommunities/pluss-core-web": "1.4.14-beta.0",
38
+ "@plusscommunities/pluss-core-web": "1.4.22",
39
+ "@fortawesome/fontawesome-svg-core": "^6.4.0",
40
+ "@fortawesome/free-solid-svg-icons": "^6.4.0",
41
+ "@fortawesome/react-fontawesome": "^0.2.0",
39
42
  "lodash": "^4.17.4",
40
43
  "moment": "^2.18.1",
41
44
  "react": "^16.14.0",
package/src/apis/index.js CHANGED
@@ -1 +1,7 @@
1
+ import { PlussCore } from '../feature.config';
2
+
3
+ const { Apis } = PlussCore;
4
+
5
+ export const analyticsActions = Apis.analyticsActions;
6
+
1
7
  export * from './circleActions';
@@ -0,0 +1,214 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import moment from 'moment';
3
+ import { faEye, faMessage, faPeopleArrows, faUserGroup, faUserTie, faUsers, faUsersRectangle } from '@fortawesome/free-solid-svg-icons';
4
+ import { connect } from 'react-redux';
5
+ import { analyticsActions } from '../apis';
6
+ import { values } from '../values.config';
7
+ import { PlussCore } from '../feature.config';
8
+
9
+ const { Analytics, Session, Components } = PlussCore;
10
+
11
+ const getInitialState = () => ({
12
+ isLoading: true,
13
+ messages: 0,
14
+ prevMessages: 0,
15
+ staffMessages: 0,
16
+ prevStaffMessages: 0,
17
+ residentMessages: 0,
18
+ prevResidentMessages: 0,
19
+ groupMessages: 0,
20
+ prevGroupMessages: 0,
21
+ privateMessages: 0,
22
+ prevPrivateMessages: 0,
23
+ activeGroups: 0,
24
+ prevActiveGroups: 0,
25
+ });
26
+
27
+ // AnalyticsHub Component
28
+ const AnalyticsHub = ({ startTime, endTime, auth, prevText, dayCount, strings }) => {
29
+ const [analyticsData, setAnalyticsData] = useState(getInitialState());
30
+ const [isExportOpen, setIsExportOpen] = useState(false);
31
+
32
+ const hasAccess = Session.validateAccess(auth.site, values.permissionNewsletter, auth);
33
+ if (!hasAccess) {
34
+ return null;
35
+ }
36
+
37
+ const featureTitle = ((key) => {
38
+ if (!strings || !strings.sideNav || !strings.sideNav[key]) {
39
+ return values.textFeatureTitle;
40
+ }
41
+ return strings.sideNav[key];
42
+ })();
43
+
44
+ const exportColumns = [
45
+ { label: 'Select All', key: '' },
46
+ { label: 'Start Date', key: 'startDate' },
47
+ { label: 'End Date', key: 'endDate' },
48
+ { label: 'Messages', key: 'messages' },
49
+ { label: 'Messages by Staff', key: 'staffMessages' },
50
+ { label: 'Messages by Primary Users', key: 'residentMessages' },
51
+ { label: 'Group Messages', key: 'groupMessages' },
52
+ { label: 'Private Messages', key: 'privateMessages' },
53
+ { label: 'Groups with Messages', key: 'activeGroups' },
54
+ ];
55
+
56
+ useEffect(() => {
57
+ getData();
58
+ }, [startTime, endTime]);
59
+
60
+ const getData = async () => {
61
+ setAnalyticsData(getInitialState());
62
+ // Load analytics data here using startTime and endTime
63
+ const timeDifference = endTime - startTime;
64
+ const [currentStatsResponse, prevStatsResponse] = await Promise.all([
65
+ analyticsActions.getAggregateEntityStats(auth.site, values.entityKey, startTime, endTime, true),
66
+ analyticsActions.getAggregateEntityStats(auth.site, values.entityKey, startTime - timeDifference, startTime, true),
67
+ ]);
68
+
69
+ const data = {
70
+ messages: Analytics.countActivities(currentStatsResponse.data, 'Message', 'total'),
71
+ prevMessages: Analytics.countActivities(prevStatsResponse.data, 'Message', 'total'),
72
+ staffMessages: Analytics.countActivities(currentStatsResponse.data, 'StaffMessage', 'total'),
73
+ prevStaffMessages: Analytics.countActivities(prevStatsResponse.data, 'StaffMessage', 'total'),
74
+ residentMessages: Analytics.countActivities(currentStatsResponse.data, 'ResidentMessage', 'total'),
75
+ prevResidentMessages: Analytics.countActivities(prevStatsResponse.data, 'ResidentMessage', 'total'),
76
+ groupMessages: Analytics.countActivities(currentStatsResponse.data, 'GroupMessage', 'total'),
77
+ prevGroupMessages: Analytics.countActivities(prevStatsResponse.data, 'GroupMessage', 'total'),
78
+ privateMessages: Analytics.countActivities(currentStatsResponse.data, 'PrivateMessage', 'total'),
79
+ prevPrivateMessages: Analytics.countActivities(prevStatsResponse.data, 'PrivateMessage', 'total'),
80
+ activeGroups: Analytics.countActivities(currentStatsResponse.data, 'GroupMessage', 'unique'),
81
+ prevActiveGroups: Analytics.countActivities(prevStatsResponse.data, 'GroupMessage', 'unique'),
82
+ // uniquePageView: Analytics.countActivities(currentStatsResponse.data, 'UniquePageView', 'uniquearray'),
83
+ // prevUniquePageView: Analytics.countActivities(prevStatsResponse.data, 'UniquePageView', 'uniquearray'),
84
+ isLoading: false,
85
+ };
86
+ setAnalyticsData(data);
87
+ };
88
+
89
+ const isReadyToOpenCSV = () => {
90
+ return !analyticsData.isLoading;
91
+ };
92
+
93
+ const getExportSource = () => {
94
+ return [
95
+ {
96
+ startDate: moment(startTime + 1).format('D-MM-YYYY'),
97
+ endDate: moment(endTime).format('D-MM-YYYY'),
98
+ messages: analyticsData.messages,
99
+ staffMessages: analyticsData.staffMessages,
100
+ residentMessages: analyticsData.residentMessages,
101
+ groupMessages: analyticsData.groupMessages,
102
+ privateMessages: analyticsData.privateMessages,
103
+ activeGroups: analyticsData.activeGroups,
104
+ },
105
+ ];
106
+ };
107
+
108
+ const csvPopup = () => {
109
+ if (!isExportOpen) {
110
+ return null;
111
+ }
112
+ const source = getExportSource();
113
+ return (
114
+ <Components.ExportCsvPopup
115
+ onClose={() => {
116
+ setIsExportOpen(false);
117
+ }}
118
+ columns={exportColumns}
119
+ source={source}
120
+ filename={`${values.analyticsKey}analytics_${source[0].startDate}_${source[0].endDate}.csv`}
121
+ />
122
+ );
123
+ };
124
+
125
+ return (
126
+ <div className="dashboardSection">
127
+ {csvPopup()}
128
+ <div>
129
+ <Components.Text type="h4" className="inlineBlock marginRight-40">
130
+ {featureTitle}
131
+ </Components.Text>
132
+ <Components.Button
133
+ inline
134
+ buttonType="primaryAction"
135
+ onClick={() => {
136
+ if (!isReadyToOpenCSV()) return;
137
+ setIsExportOpen(true);
138
+ }}
139
+ isActive={isReadyToOpenCSV()}
140
+ leftIcon="file-code-o"
141
+ >
142
+ Export CSV
143
+ </Components.Button>
144
+ </div>
145
+ <div className="analyticsSection dashboardSection_content">
146
+ <Components.StatBox
147
+ title="Groups with Messages"
148
+ icon={faUserGroup}
149
+ value={analyticsData.activeGroups}
150
+ previousValue={analyticsData.prevActiveGroups}
151
+ prevText={prevText}
152
+ viewGraphLink={`/chart?entity=${values.entityKey}&startTime=${startTime}&endTime=${endTime}&key=GroupMessage&countType=unique&dayCount=${dayCount}`}
153
+ isLoading={analyticsData.isLoading}
154
+ />
155
+ <Components.StatBox
156
+ title="Messages"
157
+ icon={faMessage}
158
+ value={analyticsData.messages}
159
+ previousValue={analyticsData.prevMessages}
160
+ prevText={prevText}
161
+ viewGraphLink={`/chart?entity=${values.entityKey}&startTime=${startTime}&endTime=${endTime}&key=Message&countType=total&dayCount=${dayCount}`}
162
+ isLoading={analyticsData.isLoading}
163
+ />
164
+ <Components.StatBox
165
+ title="Messages by Staff"
166
+ icon={faUserTie}
167
+ value={analyticsData.staffMessages}
168
+ previousValue={analyticsData.prevStaffMessages}
169
+ prevText={prevText}
170
+ viewGraphLink={`/chart?entity=${values.entityKey}&startTime=${startTime}&endTime=${endTime}&key=StaffMessage&countType=total&dayCount=${dayCount}`}
171
+ isLoading={analyticsData.isLoading}
172
+ />
173
+ <Components.StatBox
174
+ title="Messages by Primary Users"
175
+ icon={faUsers}
176
+ value={analyticsData.residentMessages}
177
+ previousValue={analyticsData.prevResidentMessages}
178
+ prevText={prevText}
179
+ viewGraphLink={`/chart?entity=${values.entityKey}&startTime=${startTime}&endTime=${endTime}&key=ResidentMessage&countType=total&dayCount=${dayCount}`}
180
+ isLoading={analyticsData.isLoading}
181
+ />
182
+ <Components.StatBox
183
+ title="Group Messages"
184
+ icon={faUsersRectangle}
185
+ value={analyticsData.groupMessages}
186
+ previousValue={analyticsData.prevGroupMessages}
187
+ prevText={prevText}
188
+ viewGraphLink={`/chart?entity=${values.entityKey}&startTime=${startTime}&endTime=${endTime}&key=GroupMessage&countType=total&dayCount=${dayCount}`}
189
+ isLoading={analyticsData.isLoading}
190
+ />
191
+ <Components.StatBox
192
+ title="Private Messages"
193
+ icon={faPeopleArrows}
194
+ value={analyticsData.privateMessages}
195
+ previousValue={analyticsData.prevPrivateMessages}
196
+ prevText={prevText}
197
+ viewGraphLink={`/chart?entity=${values.entityKey}&startTime=${startTime}&endTime=${endTime}&key=PrivateMessage&countType=total&dayCount=${dayCount}`}
198
+ isLoading={analyticsData.isLoading}
199
+ />
200
+ </div>
201
+ </div>
202
+ );
203
+ };
204
+
205
+ const mapStateToProps = (state) => {
206
+ const { auth } = state;
207
+ return {
208
+ auth,
209
+ strings: (state.strings && state.strings.config) || {},
210
+ };
211
+ };
212
+
213
+ const toExport = connect(mapStateToProps, {})(AnalyticsHub);
214
+ export { toExport as AnalyticsHub };
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import Circles from './screens/Circles';
2
2
  import AddCircle from './screens/AddCircle';
3
3
  import Circle from './screens/Circle';
4
+ import { AnalyticsHub } from './components/AnalyticsHub.js';
4
5
  import CircleReducer from './reducers/CircleReducer';
5
6
  import { values } from './values.config';
6
7
 
@@ -13,3 +14,4 @@ export { default as PreviewWidget } from './components/PreviewWidget';
13
14
  export { default as PreviewFull } from './components/PreviewFull';
14
15
  export { default as PreviewGrid } from './components/PreviewGrid';
15
16
  export { default as FeaturePickerContent } from './components/FeaturePickerContent';
17
+ export const Analytics = [AnalyticsHub];
@@ -25,6 +25,7 @@ class Circle extends Component {
25
25
  images: [],
26
26
  files: [],
27
27
  membersExpanded: true,
28
+ replyingTo: null,
28
29
  };
29
30
  }
30
31
 
@@ -189,6 +190,16 @@ class Circle extends Component {
189
190
  });
190
191
  };
191
192
 
193
+ onReply = (m) => {
194
+ this.setState({
195
+ replyingTo: m,
196
+ });
197
+ if (m) {
198
+ const input = document.getElementById('messageInput');
199
+ if (input) input.focus();
200
+ }
201
+ };
202
+
192
203
  isMember() {
193
204
  const audience = this.getCircle().Audience || [];
194
205
  return _.some(audience, (u) => {
@@ -268,6 +279,10 @@ class Circle extends Component {
268
279
  return;
269
280
  }
270
281
 
282
+ if (this.state.replyingTo) {
283
+ message.replyingTo = this.state.replyingTo;
284
+ }
285
+
271
286
  const clonedMessage = _.cloneDeep(message);
272
287
  clonedMessage.uploading = true;
273
288
 
@@ -287,6 +302,7 @@ class Circle extends Component {
287
302
  fileInputShowing: false,
288
303
  });
289
304
  setTimeout(() => {
305
+ this.onReply(null);
290
306
  this.scrollToBottom();
291
307
  this.imageInput && this.imageInput.getWrappedInstance().setValue(null);
292
308
  this.fileInput && this.fileInput.getWrappedInstance().setValue(null);
@@ -391,7 +407,15 @@ class Circle extends Component {
391
407
  <div className="message_bubbleContainer">
392
408
  <Components.Text type="body" className="message_name">
393
409
  {m.user.name}
410
+ {m.replyingTo ? ` replied to ${m.replyingTo.user.name}` : ''}
394
411
  </Components.Text>
412
+ {m.replyingTo && (
413
+ <div className="message_replyBubble">
414
+ <Components.Text type="body" className="message_text">
415
+ {Helper.toParagraphed((m.replyingTo.text || '').substr(0, 100))}
416
+ </Components.Text>
417
+ </div>
418
+ )}
395
419
  <div className="message_bubble">
396
420
  <Components.Text type="body" className="message_text">
397
421
  {Helper.toParagraphed(m.text)}
@@ -411,6 +435,16 @@ class Circle extends Component {
411
435
  })}
412
436
  </div>
413
437
  </div>
438
+ <div className="message_reply">
439
+ <Components.Text
440
+ type="button"
441
+ onClick={() => {
442
+ this.onReply(m);
443
+ }}
444
+ >
445
+ Reply
446
+ </Components.Text>
447
+ </div>
414
448
  </div>
415
449
  </div>
416
450
  </div>
@@ -512,6 +546,31 @@ class Circle extends Component {
512
546
  );
513
547
  }
514
548
 
549
+ renderReplyTo() {
550
+ if (!this.state.replyingTo) {
551
+ return null;
552
+ }
553
+ const m = this.state.replyingTo;
554
+ return (
555
+ <div className="chat_replyTo">
556
+ <div className="chat_replyTo_container">
557
+ <Components.Text type="h5">Replying to {m && m.user && !_.isEmpty(m.user.displayName) ? m.user.displayName : ''}</Components.Text>
558
+ {m && !_.isEmpty(m.text) && <Components.Text type="body">{m.text.substr(0, 50)}</Components.Text>}
559
+ </div>
560
+ <div className="chat_replyTo_remove">
561
+ <Components.SVGIcon
562
+ className="removeIcon"
563
+ icon="close"
564
+ onClick={() => {
565
+ this.onReply(null);
566
+ }}
567
+ colour={Colours.COLOUR_DUSK}
568
+ />
569
+ </div>
570
+ </div>
571
+ );
572
+ }
573
+
515
574
  render() {
516
575
  return (
517
576
  <Components.OverlayPage fullPage fullPageStyle={{ display: 'flex', flexDirection: 'column' }}>
@@ -562,6 +621,7 @@ class Circle extends Component {
562
621
  </Components.Header>
563
622
  <div className="chat">
564
623
  <div className="chat_newMessage">{this.renderChatInput()}</div>
624
+ {this.renderReplyTo()}
565
625
  <div ref={(ref) => (this.chat = ref)} className="chat_messages">
566
626
  {_.isEmpty(this.state.messages) && !_.isEmpty(this.state.messageDate) && this.renderEmptyDate()}
567
627
  {this.state.messages.map((m) => {