2019-06-28 00:46:14 +08:00
|
|
|
import React, { PureComponent } from 'react';
|
2018-10-16 03:58:07 +08:00
|
|
|
import PropTypes from 'prop-types';
|
2018-10-10 23:49:58 +08:00
|
|
|
import _ from 'lodash';
|
2018-10-16 03:05:50 +08:00
|
|
|
import { defineMessages, injectIntl } from 'react-intl';
|
2018-10-24 22:17:13 +08:00
|
|
|
import Button from '/imports/ui/components/button/component';
|
2021-03-23 14:30:37 +08:00
|
|
|
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
|
2018-10-10 23:49:58 +08:00
|
|
|
import { styles } from './styles';
|
2018-11-02 02:20:35 +08:00
|
|
|
import Service from './service';
|
2018-10-10 23:49:58 +08:00
|
|
|
|
2018-10-16 03:05:50 +08:00
|
|
|
const intlMessages = defineMessages({
|
|
|
|
usersTitle: {
|
|
|
|
id: 'app.poll.liveResult.usersTitle',
|
|
|
|
description: 'heading label for poll users',
|
|
|
|
},
|
|
|
|
responsesTitle: {
|
|
|
|
id: 'app.poll.liveResult.responsesTitle',
|
|
|
|
description: 'heading label for poll responses',
|
|
|
|
},
|
2018-10-24 22:17:13 +08:00
|
|
|
publishLabel: {
|
|
|
|
id: 'app.poll.publishLabel',
|
|
|
|
description: 'label for the publish button',
|
|
|
|
},
|
|
|
|
backLabel: {
|
|
|
|
id: 'app.poll.backLabel',
|
|
|
|
description: 'label for the return to poll options button',
|
|
|
|
},
|
2019-04-18 00:36:04 +08:00
|
|
|
doneLabel: {
|
|
|
|
id: 'app.createBreakoutRoom.doneLabel',
|
|
|
|
description: 'label shown when all users have responded',
|
|
|
|
},
|
|
|
|
waitingLabel: {
|
|
|
|
id: 'app.poll.waitingLabel',
|
|
|
|
description: 'label shown while waiting for responses',
|
|
|
|
},
|
2021-05-26 19:34:48 +08:00
|
|
|
secretPollLabel: {
|
|
|
|
id: 'app.poll.liveResult.secretLabel',
|
|
|
|
description: 'label shown instead of users in poll responses if poll is secret',
|
|
|
|
},
|
2018-10-16 03:05:50 +08:00
|
|
|
});
|
|
|
|
|
2019-04-18 00:36:04 +08:00
|
|
|
const getResponseString = (obj) => {
|
|
|
|
const { children } = obj.props;
|
|
|
|
if (typeof children !== 'string') {
|
|
|
|
return getResponseString(children[1]);
|
|
|
|
}
|
2020-06-04 20:18:49 +08:00
|
|
|
|
2019-04-18 00:36:04 +08:00
|
|
|
return children;
|
|
|
|
};
|
|
|
|
|
2019-06-28 00:46:14 +08:00
|
|
|
class LiveResult extends PureComponent {
|
2019-01-17 02:25:24 +08:00
|
|
|
static getDerivedStateFromProps(nextProps) {
|
2019-05-23 02:00:44 +08:00
|
|
|
const {
|
2021-06-12 00:55:53 +08:00
|
|
|
currentPoll, intl, pollAnswerIds, usernames, isDefaultPoll,
|
2019-05-23 02:00:44 +08:00
|
|
|
} = nextProps;
|
2018-10-31 03:18:32 +08:00
|
|
|
|
|
|
|
if (!currentPoll) return null;
|
|
|
|
|
2019-01-17 02:25:24 +08:00
|
|
|
const {
|
2021-06-12 00:55:53 +08:00
|
|
|
answers, responses, users, numRespondents, pollType
|
2019-01-17 02:25:24 +08:00
|
|
|
} = currentPoll;
|
2018-10-31 03:18:32 +08:00
|
|
|
|
2021-06-12 00:55:53 +08:00
|
|
|
const defaultPoll = isDefaultPoll(pollType);
|
|
|
|
|
2021-03-19 00:46:49 +08:00
|
|
|
const currentPollQuestion = (currentPoll.question) ? currentPoll.question : '';
|
|
|
|
|
2018-10-31 03:18:32 +08:00
|
|
|
let userAnswers = responses
|
|
|
|
? [...users, ...responses.map(u => u.userId)]
|
|
|
|
: [...users];
|
|
|
|
|
2021-04-19 21:58:33 +08:00
|
|
|
userAnswers = userAnswers.map(id => usernames[id])
|
2018-10-31 03:18:32 +08:00
|
|
|
.map((user) => {
|
2020-06-04 20:18:49 +08:00
|
|
|
let answer = '';
|
2018-10-31 03:18:32 +08:00
|
|
|
|
|
|
|
if (responses) {
|
|
|
|
const response = responses.find(r => r.userId === user.userId);
|
|
|
|
if (response) answer = answers[response.answerId].key;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: user.name,
|
|
|
|
answer,
|
|
|
|
};
|
|
|
|
})
|
2018-11-02 02:20:35 +08:00
|
|
|
.sort(Service.sortUsers)
|
2019-05-23 02:00:44 +08:00
|
|
|
.reduce((acc, user) => {
|
|
|
|
const formattedMessageIndex = user.answer.toLowerCase();
|
|
|
|
return ([
|
|
|
|
...acc,
|
|
|
|
(
|
|
|
|
<tr key={_.uniqueId('stats-')}>
|
|
|
|
<td className={styles.resultLeft}>{user.name}</td>
|
2021-03-08 07:53:58 +08:00
|
|
|
<td data-test="receivedAnswer" className={styles.resultRight}>
|
2019-05-23 02:00:44 +08:00
|
|
|
{
|
2021-06-12 00:55:53 +08:00
|
|
|
defaultPoll && pollAnswerIds[formattedMessageIndex]
|
2019-05-23 02:00:44 +08:00
|
|
|
? intl.formatMessage(pollAnswerIds[formattedMessageIndex])
|
|
|
|
: user.answer
|
|
|
|
}
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
}, []);
|
2018-10-31 03:18:32 +08:00
|
|
|
|
2018-10-10 23:49:58 +08:00
|
|
|
const pollStats = [];
|
|
|
|
|
2021-03-23 14:30:37 +08:00
|
|
|
answers.reduce(caseInsensitiveReducer, []).map((obj) => {
|
2019-05-23 02:00:44 +08:00
|
|
|
const formattedMessageIndex = obj.key.toLowerCase();
|
2018-10-31 21:28:32 +08:00
|
|
|
const pct = Math.round(obj.numVotes / numRespondents * 100);
|
2019-04-15 04:23:14 +08:00
|
|
|
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
|
|
|
|
|
|
|
|
const calculatedWidth = {
|
|
|
|
width: pctFotmatted,
|
|
|
|
};
|
2018-10-31 21:28:32 +08:00
|
|
|
|
2018-12-18 23:15:51 +08:00
|
|
|
return pollStats.push(
|
|
|
|
<div className={styles.main} key={_.uniqueId('stats-')}>
|
|
|
|
<div className={styles.left}>
|
2019-05-23 02:00:44 +08:00
|
|
|
{
|
2021-06-12 00:55:53 +08:00
|
|
|
defaultPoll && pollAnswerIds[formattedMessageIndex]
|
2019-05-23 02:00:44 +08:00
|
|
|
? intl.formatMessage(pollAnswerIds[formattedMessageIndex])
|
|
|
|
: obj.key
|
|
|
|
}
|
2018-12-18 23:15:51 +08:00
|
|
|
</div>
|
|
|
|
<div className={styles.center}>
|
2019-04-15 04:23:14 +08:00
|
|
|
<div className={styles.barShade} style={calculatedWidth} />
|
|
|
|
<div className={styles.barVal}>{obj.numVotes || 0}</div>
|
2018-12-18 23:15:51 +08:00
|
|
|
</div>
|
|
|
|
<div className={styles.right}>
|
2019-04-15 04:23:14 +08:00
|
|
|
{pctFotmatted}
|
2018-12-18 23:15:51 +08:00
|
|
|
</div>
|
|
|
|
</div>,
|
|
|
|
);
|
2018-10-31 21:28:32 +08:00
|
|
|
});
|
2018-10-10 23:49:58 +08:00
|
|
|
|
2019-01-17 02:25:24 +08:00
|
|
|
return {
|
|
|
|
userAnswers,
|
|
|
|
pollStats,
|
2021-03-19 00:46:49 +08:00
|
|
|
currentPollQuestion,
|
2019-01-17 02:25:24 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
userAnswers: null,
|
|
|
|
pollStats: null,
|
2021-03-19 00:46:49 +08:00
|
|
|
currentPollQuestion: null,
|
2019-01-17 02:25:24 +08:00
|
|
|
};
|
2018-10-10 23:49:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2018-10-24 22:17:13 +08:00
|
|
|
const {
|
2019-06-28 00:46:14 +08:00
|
|
|
isMeteorConnected,
|
|
|
|
intl,
|
|
|
|
stopPoll,
|
|
|
|
handleBackClick,
|
|
|
|
currentPoll,
|
2018-10-24 22:17:13 +08:00
|
|
|
} = this.props;
|
2018-10-16 03:05:50 +08:00
|
|
|
|
2021-03-19 00:46:49 +08:00
|
|
|
const { userAnswers, pollStats, currentPollQuestion } = this.state;
|
2019-01-17 02:25:24 +08:00
|
|
|
|
2019-04-18 00:36:04 +08:00
|
|
|
let waiting;
|
|
|
|
let userCount = 0;
|
|
|
|
let respondedCount = 0;
|
|
|
|
|
|
|
|
if (userAnswers) {
|
|
|
|
userCount = userAnswers.length;
|
|
|
|
userAnswers.map((user) => {
|
|
|
|
const response = getResponseString(user);
|
2020-06-04 20:18:49 +08:00
|
|
|
if (response === '') return user;
|
2019-04-18 00:36:04 +08:00
|
|
|
respondedCount += 1;
|
|
|
|
return user;
|
|
|
|
});
|
|
|
|
|
2019-04-23 02:27:59 +08:00
|
|
|
waiting = respondedCount !== userAnswers.length && currentPoll;
|
2019-04-18 00:36:04 +08:00
|
|
|
}
|
|
|
|
|
2018-10-10 23:49:58 +08:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<div className={styles.stats}>
|
2021-03-19 00:46:49 +08:00
|
|
|
{currentPollQuestion ? <span className={styles.title}>{currentPollQuestion}</span> : null}
|
2020-09-22 06:52:38 +08:00
|
|
|
<div className={styles.status}>
|
|
|
|
{waiting
|
|
|
|
? (
|
|
|
|
<span>
|
|
|
|
{`${intl.formatMessage(intlMessages.waitingLabel, {
|
|
|
|
0: respondedCount,
|
|
|
|
1: userCount,
|
|
|
|
})} `}
|
|
|
|
</span>
|
|
|
|
)
|
|
|
|
: <span>{intl.formatMessage(intlMessages.doneLabel)}</span>}
|
|
|
|
{waiting
|
|
|
|
? <span className={styles.connectingAnimation} /> : null}
|
|
|
|
</div>
|
2019-01-17 02:25:24 +08:00
|
|
|
{pollStats}
|
2018-10-10 23:49:58 +08:00
|
|
|
</div>
|
2020-09-21 20:07:36 +08:00
|
|
|
{currentPoll && currentPoll.answers.length > 0
|
2019-01-17 02:25:24 +08:00
|
|
|
? (
|
|
|
|
<Button
|
2019-06-28 00:46:14 +08:00
|
|
|
disabled={!isMeteorConnected}
|
2019-01-17 02:25:24 +08:00
|
|
|
onClick={() => {
|
2020-01-30 02:36:17 +08:00
|
|
|
Session.set('pollInitiated', false);
|
2019-08-30 00:26:07 +08:00
|
|
|
Service.publishPoll();
|
2019-01-17 02:25:24 +08:00
|
|
|
stopPoll();
|
|
|
|
}}
|
|
|
|
label={intl.formatMessage(intlMessages.publishLabel)}
|
2020-03-20 22:42:04 +08:00
|
|
|
data-test="publishLabel"
|
2019-01-17 02:25:24 +08:00
|
|
|
color="primary"
|
|
|
|
className={styles.btn}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<Button
|
2019-06-28 00:46:14 +08:00
|
|
|
disabled={!isMeteorConnected}
|
2019-01-17 02:25:24 +08:00
|
|
|
onClick={() => {
|
|
|
|
handleBackClick();
|
|
|
|
}}
|
|
|
|
label={intl.formatMessage(intlMessages.backLabel)}
|
2021-05-11 22:22:08 +08:00
|
|
|
color="primary"
|
2019-01-17 02:25:24 +08:00
|
|
|
className={styles.btn}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
2021-05-26 19:34:48 +08:00
|
|
|
<div className={styles.separator} />
|
|
|
|
{ currentPoll && !currentPoll.secretPoll
|
|
|
|
? (
|
|
|
|
<table>
|
|
|
|
<tbody>
|
|
|
|
<tr>
|
|
|
|
<th className={styles.theading}>{intl.formatMessage(intlMessages.usersTitle)}</th>
|
|
|
|
<th className={styles.theading}>{intl.formatMessage(intlMessages.responsesTitle)}</th>
|
|
|
|
</tr>
|
|
|
|
{userAnswers}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
) : (
|
|
|
|
currentPoll ? (<div>{intl.formatMessage(intlMessages.secretPollLabel)}</div>) : null
|
|
|
|
)}
|
2018-10-10 23:49:58 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-16 03:05:50 +08:00
|
|
|
export default injectIntl(LiveResult);
|
2018-10-16 03:58:07 +08:00
|
|
|
|
2019-01-17 02:25:24 +08:00
|
|
|
LiveResult.defaultProps = { currentPoll: null };
|
|
|
|
|
2018-10-16 03:58:07 +08:00
|
|
|
LiveResult.propTypes = {
|
|
|
|
intl: PropTypes.shape({
|
|
|
|
formatMessage: PropTypes.func.isRequired,
|
|
|
|
}).isRequired,
|
2019-01-17 02:25:24 +08:00
|
|
|
currentPoll: PropTypes.oneOfType([
|
|
|
|
PropTypes.arrayOf(Object),
|
|
|
|
PropTypes.shape({
|
|
|
|
answers: PropTypes.arrayOf(PropTypes.object),
|
|
|
|
users: PropTypes.arrayOf(PropTypes.string),
|
|
|
|
}),
|
|
|
|
]),
|
2018-12-18 23:15:51 +08:00
|
|
|
stopPoll: PropTypes.func.isRequired,
|
|
|
|
handleBackClick: PropTypes.func.isRequired,
|
2018-10-16 03:58:07 +08:00
|
|
|
};
|