@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/dist/index.cjs.js +412 -95
- package/dist/index.esm.js +412 -97
- package/dist/index.umd.js +414 -99
- package/package.json +5 -2
- package/src/apis/index.js +6 -0
- package/src/components/AnalyticsHub.js +214 -0
- package/src/index.js +2 -0
- package/src/screens/Circle.js +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plusscommunities/pluss-circles-web-groups",
|
|
3
|
-
"version": "1.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.
|
|
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
|
@@ -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];
|
package/src/screens/Circle.js
CHANGED
|
@@ -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) => {
|