@plusscommunities/pluss-feeds-web 1.0.7 → 1.0.9-beta.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.
@@ -1,685 +0,0 @@
1
- import React, { Component } from 'react';
2
- import { Table } from 'react-bootstrap';
3
- import { connect } from 'react-redux';
4
- import { withRouter } from 'react-router';
5
- import { Link } from 'react-router-dom';
6
- import FontAwesome from 'react-fontawesome';
7
- import moment from 'moment';
8
- import _ from 'lodash';
9
- import { feedsLoaded, removeFeed, usersLoaded } from '../actions';
10
- import { PlussCore } from '../feature.config';
11
- import { feedActions, userActions } from '../apis';
12
- import { Colours } from '@plusscommunities/pluss-core-web';
13
- import { values } from '../values.config';
14
-
15
- const { Session, Components, Analytics } = PlussCore;
16
-
17
- class FeedList extends Component {
18
- constructor(props) {
19
- super(props);
20
- this.state = {
21
- sortColumn: 'created',
22
- sortDesc: true,
23
- selectedTimeFilter: Analytics.getAnalyticsFilterOptions()[1],
24
- selectedTimeFilterStart: null,
25
- selectedTimeFilterEnd: null,
26
- selectedTimeFilterText: null,
27
- selectedUserFilter: null,
28
- selectedUserFilterText: null,
29
- selectedUser: null,
30
- selectedAuthorFilter: null,
31
- selectedAuthorFilterText: null,
32
- selectedAuthor: null,
33
- selectedTypeFilter: null,
34
- };
35
-
36
- this.exportColumns = [
37
- { label: 'Select All', key: '' },
38
- { label: 'System Id', key: 'id' },
39
- { label: 'Type', key: 'type' },
40
- { label: 'Title', key: 'title' },
41
- { label: 'Text', key: 'text' },
42
- { label: 'User Name', key: 'userName' },
43
- { label: 'User ID', key: 'userId' },
44
- { label: 'Author Name', key: 'authorName' },
45
- { label: 'Author ID', key: 'authorId' },
46
- { label: 'Post Time', key: 'created' },
47
- ];
48
- }
49
-
50
- UNSAFE_componentWillMount() {
51
- Session.checkLoggedIn(this);
52
- }
53
-
54
- componentDidMount() {
55
- this.getUsers();
56
- this.getFeeds();
57
- }
58
-
59
- getUsers = async () => {
60
- const { auth } = this.props;
61
- try {
62
- const res = await userActions.fetchUsers(auth.site);
63
- if (res.data != null && !_.isEmpty(res.data.results.Items) && res.data.results.Items[0].site === auth.site) {
64
- this.props.usersLoaded(res.data.results.Items);
65
- }
66
- } catch (error) {
67
- console.error('getUsers', error);
68
- }
69
- };
70
-
71
- getFeeds = async () => {
72
- const { auth } = this.props;
73
- try {
74
- const res = await feedActions.getFeedsRecursive(auth.site);
75
- if (!_.isEmpty(res) && res[0].site === auth.site) {
76
- this.props.feedsLoaded(res);
77
- }
78
- } catch (error) {
79
- console.error('getFeeds', error);
80
- }
81
- };
82
-
83
- sortByCol = (col) => {
84
- const { sortColumn, sortDesc } = this.state;
85
- if (sortColumn === col) {
86
- this.setState({ sortDesc: !sortDesc });
87
- } else {
88
- this.setState({ sortColumn: col, sortDesc: false });
89
- }
90
- };
91
-
92
- onRemoveFeed = async (feed) => {
93
- if (window.confirm(values.textAreYouSureYouWantToDelete)) {
94
- this.props.removeFeed(feed.id);
95
- try {
96
- await feedActions.deleteFeed(feed.id);
97
- this.getFeeds();
98
- } catch (error) {
99
- console.log('onRemoveFeed', error);
100
- alert('Something went wrong with the request. Please try again.');
101
- }
102
- }
103
- };
104
-
105
- openFilter = (filter) => {
106
- this.setState({ filterOpen: filter });
107
- };
108
-
109
- closeFilter = () => {
110
- this.setState({ filterOpen: null });
111
- };
112
-
113
- selectTypeFilter = (filter) => {
114
- this.setState({ selectedTypeFilter: filter });
115
- this.closeFilter();
116
- };
117
-
118
- timeFilterChanged = (selectedTimeFilter) => {
119
- this.setState({ selectedTimeFilter });
120
- };
121
-
122
- timeFilterDateRangeChanged = (startDate, endDate) => {
123
- this.setState({ timeFilterStart: startDate, timeFilterEnd: endDate });
124
- };
125
-
126
- isValidTimeFilter = () => {
127
- if (!this.state.selectedTimeFilter) return false;
128
- if (this.state.selectedTimeFilter.dayCount > 0) return true;
129
- return moment(this.state.timeFilterStart).startOf('d').valueOf() < moment(this.state.timeFilterEnd).endOf('d').valueOf();
130
- };
131
-
132
- saveTimeFilter = () => {
133
- if (!this.isValidTimeFilter()) return;
134
-
135
- let startTime = 0;
136
- let endTime = moment().endOf('d').valueOf();
137
- let text = '';
138
- if (this.state.selectedTimeFilter.dayCount > 0) {
139
- startTime = moment().add(-this.state.selectedTimeFilter.dayCount, 'd').startOf('d').valueOf();
140
- text = this.state.selectedTimeFilter.text;
141
- } else {
142
- const startDate = moment(this.state.timeFilterStart).startOf('d');
143
- const endDate = moment(this.state.timeFilterEnd).endOf('d');
144
- startTime = startDate.valueOf();
145
- endTime = endDate.valueOf();
146
- text = `${startDate.format('DD/MM/YYYY')} to ${endDate.format('DD/MM/YYYY')}`;
147
- }
148
- this.setState({
149
- selectedTimeFilterStart: startTime,
150
- selectedTimeFilterEnd: endTime,
151
- selectedTimeFilterText: text,
152
- });
153
- this.closeFilter();
154
- };
155
-
156
- removeTimeFilter = () => {
157
- this.setState({
158
- selectedTimeFilterStart: null,
159
- selectedTimeFilterEnd: null,
160
- selectedTimeFilterText: null,
161
- });
162
- };
163
-
164
- onHandleChange = (event) => {
165
- var stateChange = {};
166
- stateChange[event.target.getAttribute('id')] = event.target.value;
167
- this.setState(stateChange);
168
- };
169
-
170
- onSelectUser = (userKey, user = null) => {
171
- this.setState({ [userKey]: user });
172
- };
173
-
174
- removeUserFilter = (userKey) => {
175
- this.setState({
176
- [`${userKey}Filter`]: null,
177
- [`${userKey}FilterText`]: null,
178
- });
179
- // Notifiy user filter reset (for preselecting user in add)
180
- if (userKey === 'selectedUser' && this.props.onUserSelected) this.props.onUserSelected('');
181
- };
182
-
183
- saveUserFilter = (userKey) => {
184
- const selected = this.state[userKey];
185
- let userId = '';
186
- if (!selected) {
187
- this.removeUserFilter(userKey);
188
- } else {
189
- userId = selected.id || selected.Id;
190
- this.setState({
191
- [`${userKey}Filter`]: userId,
192
- [`${userKey}FilterText`]: selected.displayName,
193
- [userKey]: null,
194
- });
195
- // Notifiy user filter set (for preselecting user in add)
196
- if (userKey === 'selectedUser' && this.props.onUserSelected) this.props.onUserSelected(userId);
197
- }
198
- this.closeFilter();
199
- };
200
-
201
- getSource = () => {
202
- const { selectedTimeFilterStart, selectedTimeFilterEnd, selectedTypeFilter, selectedUserFilter, selectedAuthorFilter } = this.state;
203
- let source = this.props.source;
204
-
205
- // filter by time
206
- if (selectedTimeFilterStart && selectedTimeFilterEnd) {
207
- // console.log('getSource - time filter', {
208
- // selectedTimeFilterStart: moment(selectedTimeFilterStart).format('D MMM YYYY h:mma'),
209
- // selectedTimeFilterEnd: moment(selectedTimeFilterEnd).format('D MMM YYYY h:mma'),
210
- // });
211
- source = _.filter(source, (r) => {
212
- return r.created >= selectedTimeFilterStart && r.created <= selectedTimeFilterEnd;
213
- });
214
- }
215
-
216
- // filter by type
217
- if (selectedTypeFilter) {
218
- // console.log('getSource - type filter', { selectedTypeFilter });
219
- source = _.filter(source, (r) => {
220
- return r.type === selectedTypeFilter;
221
- });
222
- }
223
-
224
- // filter by user
225
- if (selectedUserFilter) {
226
- // console.log('getSource - user filter', { selectedUserFilter });
227
- source = _.filter(source, (r) => {
228
- return r.user.id === selectedUserFilter;
229
- });
230
- }
231
-
232
- // filter by author
233
- if (selectedAuthorFilter) {
234
- // console.log('getSource - author filter', { selectedAuthorFilter });
235
- source = _.filter(source, (r) => {
236
- return r.author.id === selectedAuthorFilter;
237
- });
238
- }
239
-
240
- if (!_.isEmpty(this.state.searchTerm)) {
241
- source = _.filter(source, (r) => {
242
- if (r.jobId && r.jobId === this.state.searchTerm) {
243
- return true;
244
- }
245
- if (r.room && r.room.toLowerCase().indexOf(this.state.searchTerm.toLowerCase()) > -1) {
246
- return true;
247
- }
248
- if (r.title && r.title.toLowerCase().indexOf(this.state.searchTerm.toLowerCase()) > -1) {
249
- return true;
250
- }
251
- return false;
252
- });
253
- }
254
-
255
- source = _.sortBy(source, (event) => {
256
- if (this.state.sortColumn === 'userName') return event.user.displayName;
257
- if (this.state.sortColumn === 'authorName') return event.author.displayName;
258
- if (this.state.sortColumn !== 'created') return event[this.state.sortColumn];
259
- return event.created;
260
- });
261
- if (this.state.sortDesc) source.reverse();
262
- return source;
263
- };
264
-
265
- getExportSource = () => {
266
- return this.getSource().map((r) => {
267
- const userName = r.user.displayName;
268
- const userId = r.user.id;
269
- const authorName = r.author.displayName;
270
- const authorId = r.author.id;
271
- const created = moment.utc(r.created).format();
272
- return { ...r, userName, userId, authorName, authorId, created };
273
- });
274
- };
275
-
276
- isExportReady = () => {
277
- return !_.isEmpty(this.getSource());
278
- };
279
-
280
- onOpenExportCsv = () => {
281
- if (!this.isExportReady()) return;
282
- this.setState({ exportCsvOpen: true });
283
- };
284
-
285
- onCloseExportCsv = () => {
286
- this.setState({ exportCsvOpen: false });
287
- };
288
-
289
- renderTimePopup() {
290
- const { selectedTimeFilter } = this.state;
291
-
292
- return (
293
- <Components.Popup
294
- title="Select Time"
295
- maxWidth={600}
296
- minWidth={400}
297
- minHeight={400}
298
- hasPadding
299
- onClose={this.closeFilter}
300
- buttons={[
301
- {
302
- type: 'primaryAction',
303
- onClick: this.saveTimeFilter,
304
- text: 'Select',
305
- isActive: this.isValidTimeFilter(),
306
- },
307
- ]}
308
- >
309
- <div style={{ minHeight: 150 }}>
310
- <Components.AnalyticsFilter
311
- defaultFilter={selectedTimeFilter}
312
- filterChanged={this.timeFilterChanged}
313
- filterDateRangeChanged={this.timeFilterDateRangeChanged}
314
- />
315
- </div>
316
- </Components.Popup>
317
- );
318
- }
319
-
320
- renderUserPopup(userKey, label) {
321
- const { userSearch } = this.state;
322
- const selected = this.state[userKey];
323
-
324
- let userContent = null;
325
- if (selected) {
326
- userContent = (
327
- <div>
328
- <Components.UserListing
329
- key={selected.id || selected.Id}
330
- user={selected}
331
- rightContent={
332
- <Components.SVGIcon
333
- className="removeIcon"
334
- icon="close"
335
- onClick={() => this.onSelectUser(userKey)}
336
- colour={Colours.COLOUR_DUSK}
337
- />
338
- }
339
- />
340
- </div>
341
- );
342
- } else {
343
- userContent = (
344
- <div>
345
- <Components.GenericInput
346
- id="userSearch"
347
- type="text"
348
- // label="Search"
349
- placeholder="Search name"
350
- value={userSearch}
351
- onChange={(e) => this.onHandleChange(e)}
352
- alwaysShowLabel
353
- />
354
- {_.sortBy(this.props.users, (u) => u.displayName.toUpperCase())
355
- .filter((u) => {
356
- if (_.isEmpty(userSearch)) return true;
357
- return u.displayName.toUpperCase().indexOf(userSearch.toUpperCase()) > -1;
358
- })
359
- .map((user) => {
360
- return <Components.UserListing key={user.Id} user={user} onClick={() => this.onSelectUser(userKey, user)} />;
361
- })}
362
- </div>
363
- );
364
- }
365
- return (
366
- <Components.Popup
367
- title={`Select ${label}`}
368
- maxWidth={600}
369
- minWidth={400}
370
- hasPadding
371
- onClose={this.closeFilter}
372
- buttons={[
373
- {
374
- type: 'primaryAction',
375
- onClick: () => this.saveUserFilter(userKey),
376
- text: 'Select',
377
- isActive: true,
378
- },
379
- ]}
380
- >
381
- {userContent}
382
- </Components.Popup>
383
- );
384
- }
385
-
386
- renderTypePopup() {
387
- const validTypes = _.uniq(this.props.source.map((r) => r.type)).filter((t) => t);
388
- const orderedTypes = _.sortBy(validTypes, (t) => t.toLowerCase());
389
-
390
- return (
391
- <Components.Popup title="Select Type" maxWidth={600} minWidth={400} hasPadding onClose={this.closeFilter}>
392
- {orderedTypes.map((type) => {
393
- return (
394
- <Components.Tag key={type} onClick={() => this.selectTypeFilter(type)} text={type} className="marginRight-10 marginBottom-8" />
395
- );
396
- })}
397
- </Components.Popup>
398
- );
399
- }
400
-
401
- renderFilterPopup() {
402
- if (!this.state.filterOpen) return null;
403
-
404
- if (this.state.filterOpen === 'time') return this.renderTimePopup();
405
- if (this.state.filterOpen === 'user') return this.renderUserPopup('selectedUser', 'User');
406
- if (this.state.filterOpen === 'author') return this.renderUserPopup('selectedAuthor', 'Author');
407
- if (this.state.filterOpen === 'type') return this.renderTypePopup();
408
- return null;
409
- }
410
-
411
- renderFeeds() {
412
- // Posts can only be edited or deleted by those with full admin privileges
413
- const canEdit = Session.validateAccess(this.props.auth.site, values.permissionFeedManagement, this.props.auth);
414
-
415
- return this.getSource().map((feed, index) => {
416
- if (!feed) {
417
- return null;
418
- }
419
-
420
- return (
421
- <tr key={index}>
422
- <td className="table-TitleColumn">
423
- <Link to={`${values.routeFeedDetails}/${feed.id}`}>
424
- <span>{feed.title}</span>
425
- </Link>
426
- </td>
427
- <td>{moment.utc(feed.created).local().format('ddd D MMM YYYY h:mma')}</td>
428
- <td>{feed.user && feed.user.displayName}</td>
429
- <td>{feed.author && feed.author.displayName}</td>
430
- <td className="table-options">
431
- {canEdit ? (
432
- <div style={{ display: 'flex', alignItems: 'center' }}>
433
- <Link to={`${values.routeFeedDetails}/${feed.id}`}>
434
- <FontAwesome style={{ fontSize: 20, padding: 5, cursor: 'pointer' }} name="pencil" />
435
- </Link>
436
- <a onClick={() => this.onRemoveFeed(feed)}>
437
- <FontAwesome style={{ fontSize: 20, padding: 5, marginLeft: 8, cursor: 'pointer' }} name="minus-circle" />
438
- </a>
439
- </div>
440
- ) : null}
441
- </td>
442
- </tr>
443
- );
444
- });
445
- }
446
-
447
- renderSort(col) {
448
- const { sortColumn, sortDesc } = this.state;
449
- if (col !== sortColumn) return null;
450
- return <FontAwesome style={{ marginLeft: 5 }} name={sortDesc ? 'chevron-up' : 'chevron-down'} />;
451
- }
452
-
453
- sortIsActive(col) {
454
- if (col !== this.state.sortColumn) return '';
455
- return ' table--columnActive';
456
- }
457
-
458
- renderEmpty() {
459
- const title = this.props.strings[`${values.featureKey}_textTitleRequests`] || values.textTitleRequests;
460
-
461
- return (
462
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: 32 }}>
463
- <div className="emptyState" />
464
- <div className="marginTop-32" style={{ maxWidth: 500, textAlign: 'center' }}>
465
- <span className="fontRegular fontSize-13">
466
- <span className="fontHeavy text-brandingColour">{title} </span>
467
- {values.textEmptyDescription}
468
- </span>
469
- </div>
470
- <div className="marginTop-8 fontRegular fontSize-13" style={{ maxWidth: 500, textAlign: 'center' }}>
471
- {values.textEmptyExample}
472
- </div>
473
- </div>
474
- );
475
- }
476
-
477
- renderContent() {
478
- if (_.isEmpty(this.props.source)) return this.renderEmpty();
479
-
480
- return (
481
- <Table className="plussTable" striped bordered condensed hover style={{ minWidth: '100%', marginTop: 32 }}>
482
- <thead>
483
- <tr>
484
- <th
485
- className={`${this.sortIsActive('title')}`}
486
- style={{ cursor: 'pointer', minWidth: 200 }}
487
- onClick={() => {
488
- this.sortByCol('title');
489
- }}
490
- >
491
- Title{this.renderSort('title')}
492
- </th>
493
- <th
494
- className={`${this.sortIsActive('created')}`}
495
- style={{ cursor: 'pointer' }}
496
- onClick={() => {
497
- this.sortByCol('created');
498
- }}
499
- >
500
- Time{this.renderSort('created')}
501
- </th>
502
- <th
503
- className={`${this.sortIsActive('userName')}`}
504
- style={{ cursor: 'pointer' }}
505
- onClick={() => {
506
- this.sortByCol('userName');
507
- }}
508
- >
509
- Posted To{this.renderSort('userName')}
510
- </th>
511
- <th
512
- className={`${this.sortIsActive('authorName')}`}
513
- style={{ cursor: 'pointer' }}
514
- onClick={() => {
515
- this.sortByCol('authorName');
516
- }}
517
- >
518
- Author{this.renderSort('authorName')}
519
- </th>
520
- <th style={{ width: 50 }} />
521
- </tr>
522
- </thead>
523
- <tbody>{this.renderFeeds()}</tbody>
524
- </Table>
525
- );
526
- }
527
-
528
- renderFilters() {
529
- let typeFilter = <Components.Tag className="marginRight-10" onClick={() => this.openFilter('type')} text="Type" />;
530
- if (this.state.selectedTypeFilter) {
531
- typeFilter = (
532
- <Components.Tag
533
- className="marginRight-10"
534
- onClick={() => {
535
- this.openFilter('type');
536
- }}
537
- rightIcon="close"
538
- rightClick={(e) => {
539
- e.stopPropagation();
540
- this.selectTypeFilter();
541
- }}
542
- text={this.state.selectedTypeFilter}
543
- />
544
- );
545
- }
546
- let timeFilter = (
547
- <Components.Tag
548
- className="marginRight-10"
549
- onClick={() => {
550
- this.openFilter('time');
551
- }}
552
- text="Time"
553
- />
554
- );
555
- if (this.state.selectedTimeFilterText) {
556
- timeFilter = (
557
- <Components.Tag
558
- className="marginRight-10"
559
- onClick={() => {
560
- this.openFilter('time');
561
- }}
562
- rightIcon="close"
563
- rightClick={(e) => {
564
- e.stopPropagation();
565
- this.removeTimeFilter();
566
- }}
567
- text={this.state.selectedTimeFilterText}
568
- />
569
- );
570
- }
571
- let userFilter = (
572
- <Components.Tag
573
- className="marginRight-10"
574
- onClick={() => {
575
- this.openFilter('user');
576
- }}
577
- text="User"
578
- />
579
- );
580
- if (this.state.selectedUserFilter) {
581
- userFilter = (
582
- <Components.Tag
583
- className="marginRight-10"
584
- onClick={() => {
585
- this.openFilter('user');
586
- }}
587
- rightIcon="close"
588
- rightClick={(e) => {
589
- e.stopPropagation();
590
- this.removeUserFilter('selectedUser');
591
- }}
592
- text={this.state.selectedUserFilterText}
593
- />
594
- );
595
- }
596
- let authorFilter = (
597
- <Components.Tag
598
- className="marginRight-10"
599
- onClick={() => {
600
- this.openFilter('author');
601
- }}
602
- text="Author"
603
- />
604
- );
605
- if (this.state.selectedAuthorFilter) {
606
- authorFilter = (
607
- <Components.Tag
608
- className="marginRight-10"
609
- onClick={() => {
610
- this.openFilter('author');
611
- }}
612
- rightIcon="close"
613
- rightClick={(e) => {
614
- e.stopPropagation();
615
- this.removeUserFilter('selectedAuthor');
616
- }}
617
- text={this.state.selectedAuthorFilterText}
618
- />
619
- );
620
- }
621
- return (
622
- <div>
623
- <div className="marginTop-20 flex flex-between flex-center">
624
- <div className="flex flex-center">
625
- <Components.Text type="h5" className="marginRight-20">
626
- Filter by
627
- </Components.Text>
628
- {timeFilter}
629
- {userFilter}
630
- {authorFilter}
631
- {typeFilter}
632
- </div>
633
- <Components.Button
634
- inline
635
- buttonType="primaryAction"
636
- leftIcon="file-code-o"
637
- onClick={this.onOpenExportCsv}
638
- isActive={!_.isEmpty(this.getSource())}
639
- >
640
- Export CSV
641
- </Components.Button>
642
- </div>
643
- </div>
644
- );
645
- }
646
-
647
- renderCSVPopup() {
648
- if (!this.state.exportCsvOpen) {
649
- return null;
650
- }
651
-
652
- return (
653
- <Components.ExportCsvPopup
654
- onClose={this.onCloseExportCsv}
655
- columns={this.exportColumns}
656
- source={this.getExportSource()}
657
- filename="feeds.csv"
658
- />
659
- );
660
- }
661
-
662
- render() {
663
- return (
664
- <div style={{ minWidth: '100%' }}>
665
- {this.renderFilterPopup()}
666
- {this.renderCSVPopup()}
667
- {this.renderFilters()}
668
- {this.renderContent()}
669
- </div>
670
- );
671
- }
672
- }
673
-
674
- const mapStateToProps = (state) => {
675
- const { users } = state.users;
676
- const { auth } = state;
677
- return {
678
- users,
679
- feeds: state[values.reducerKey].feeds,
680
- auth,
681
- strings: (state.strings && state.strings.config) || {},
682
- };
683
- };
684
-
685
- export default connect(mapStateToProps, { feedsLoaded, removeFeed, usersLoaded })(withRouter(FeedList));