Implements some updates on Learning Dashboard
This commit is contained in:
parent
21db352ec6
commit
3e515b96e9
@ -128,6 +128,7 @@ class App extends React.Component {
|
|||||||
// Get the three most used
|
// Get the three most used
|
||||||
const mostUsedEmojis = Object
|
const mostUsedEmojis = Object
|
||||||
.entries(emojiCount)
|
.entries(emojiCount)
|
||||||
|
.filter(([, count]) => count)
|
||||||
.sort(([, countA], [, countB]) => countA - countB)
|
.sort(([, countA], [, countB]) => countA - countB)
|
||||||
.reverse()
|
.reverse()
|
||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
@ -452,7 +453,7 @@ class App extends React.Component {
|
|||||||
? <FormattedMessage id="app.learningDashboard.statusTimelineTable.title" defaultMessage="Timeline" />
|
? <FormattedMessage id="app.learningDashboard.statusTimelineTable.title" defaultMessage="Timeline" />
|
||||||
: null }
|
: null }
|
||||||
{ tab === 'polling'
|
{ tab === 'polling'
|
||||||
? <FormattedMessage id="app.learningDashboard.pollsTable.title" defaultMessage="Polling" />
|
? <FormattedMessage id="app.learningDashboard.pollsTable.title" defaultMessage="Polls" />
|
||||||
: null }
|
: null }
|
||||||
</h1>
|
</h1>
|
||||||
<div className="w-full overflow-hidden rounded-md shadow-xs border-2 border-gray-100">
|
<div className="w-full overflow-hidden rounded-md shadow-xs border-2 border-gray-100">
|
||||||
@ -482,7 +483,7 @@ class App extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className="my-8" />
|
<hr className="my-8" />
|
||||||
<div className="flex justify-between mb-8 text-xs text-gray-700 dark:text-gray-400 whitespace-nowrap flex-col sm:flex-row">
|
<div className="flex justify-between pb-8 text-xs text-gray-700 dark:text-gray-400 whitespace-nowrap flex-col sm:flex-row">
|
||||||
<div className="flex flex-col justify-center mb-4 sm:mb-0">
|
<div className="flex flex-col justify-center mb-4 sm:mb-0">
|
||||||
<p>
|
<p>
|
||||||
{
|
{
|
||||||
|
@ -18,7 +18,7 @@ class PollsTable extends React.Component {
|
|||||||
|
|
||||||
if (typeof polls === 'object' && Object.values(polls).length === 0) {
|
if (typeof polls === 'object' && Object.values(polls).length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center py-24">
|
<div className="flex flex-col items-center py-24 bg-white">
|
||||||
<div className="mb-1 p-3 rounded-full bg-blue-100 text-blue-500">
|
<div className="mb-1 p-3 rounded-full bg-blue-100 text-blue-500">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -6,22 +6,49 @@ import { getUserEmojisSummary, emojiConfigs } from '../services/EmojiService';
|
|||||||
import { getActivityScore, getSumOfTime, tsToHHmmss } from '../services/UserService';
|
import { getActivityScore, getSumOfTime, tsToHHmmss } from '../services/UserService';
|
||||||
import UserAvatar from './UserAvatar';
|
import UserAvatar from './UserAvatar';
|
||||||
|
|
||||||
|
function renderArrow(order = 'asc') {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-4 w-4 inline"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d={order === 'asc' ? 'M7 11l5-5m0 0l5 5m-5-5v12' : 'M17 13l-5 5m0 0l-5-5m5 5V6'}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class UsersTable extends React.Component {
|
class UsersTable extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
userOrder: 'asc',
|
||||||
|
onlineTimeOrder: 'desc',
|
||||||
|
talkTimeOrder: 'desc',
|
||||||
|
webcamTimeOrder: 'desc',
|
||||||
activityscoreOrder: 'desc',
|
activityscoreOrder: 'desc',
|
||||||
|
lastFieldClicked: 'userOrder',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleActivityScoreOrder() {
|
toggleOrder(field) {
|
||||||
const { activityscoreOrder } = this.state;
|
const { [field]: fieldOrder } = this.state;
|
||||||
|
const { tab } = this.props;
|
||||||
|
|
||||||
if (activityscoreOrder === 'asc') {
|
if (fieldOrder === 'asc') {
|
||||||
this.setState({ activityscoreOrder: 'desc' });
|
this.setState({ [field]: 'desc' });
|
||||||
} else {
|
} else {
|
||||||
this.setState({ activityscoreOrder: 'asc' });
|
this.setState({ [field]: 'asc' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tab === 'overview') this.setState({ lastFieldClicked: field });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -29,7 +56,10 @@ class UsersTable extends React.Component {
|
|||||||
allUsers, totalOfActivityTime, totalOfPolls, tab,
|
allUsers, totalOfActivityTime, totalOfPolls, tab,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { activityscoreOrder } = this.state;
|
const {
|
||||||
|
activityscoreOrder, userOrder, onlineTimeOrder,
|
||||||
|
talkTimeOrder, webcamTimeOrder, lastFieldClicked,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const usersEmojisSummary = {};
|
const usersEmojisSummary = {};
|
||||||
Object.values(allUsers || {}).forEach((user) => {
|
Object.values(allUsers || {}).forEach((user) => {
|
||||||
@ -46,36 +76,120 @@ class UsersTable extends React.Component {
|
|||||||
usersActivityScore[user.userKey] = getActivityScore(user, allUsers, totalOfPolls);
|
usersActivityScore[user.userKey] = getActivityScore(user, allUsers, totalOfPolls);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sortFunctions = {
|
||||||
|
userOrder(a, b) {
|
||||||
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||||
|
return userOrder === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
||||||
|
return userOrder === 'desc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
onlineTimeOrder(a, b) {
|
||||||
|
const onlineTimeA = Object.values(a.intIds).reduce((prev, intId) => (
|
||||||
|
prev + ((intId.leftOn > 0
|
||||||
|
? intId.leftOn
|
||||||
|
: (new Date()).getTime()) - intId.registeredOn)
|
||||||
|
), 0);
|
||||||
|
|
||||||
|
const onlineTimeB = Object.values(b.intIds).reduce((prev, intId) => (
|
||||||
|
prev + ((intId.leftOn > 0
|
||||||
|
? intId.leftOn
|
||||||
|
: (new Date()).getTime()) - intId.registeredOn)
|
||||||
|
), 0);
|
||||||
|
|
||||||
|
if (onlineTimeA < onlineTimeB) {
|
||||||
|
return onlineTimeOrder === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlineTimeA > onlineTimeB) {
|
||||||
|
return onlineTimeOrder === 'desc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
talkTimeOrder(a, b) {
|
||||||
|
const talkTimeA = a.talk.totalTime;
|
||||||
|
const talkTimeB = b.talk.totalTime;
|
||||||
|
|
||||||
|
if (talkTimeA < talkTimeB) {
|
||||||
|
return onlineTimeOrder === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (talkTimeA > talkTimeB) {
|
||||||
|
return onlineTimeOrder === 'desc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
webcamTimeOrder(a, b) {
|
||||||
|
const webcamTimeA = getSumOfTime(a.webcams);
|
||||||
|
const webcamTimeB = getSumOfTime(b.webcams);
|
||||||
|
console.log(webcamTimeA, webcamTimeB);
|
||||||
|
|
||||||
|
if (webcamTimeA < webcamTimeB) {
|
||||||
|
return onlineTimeOrder === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webcamTimeA > webcamTimeB) {
|
||||||
|
return onlineTimeOrder === 'desc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
activityscoreOrder(a, b) {
|
||||||
|
if (usersActivityScore[a.userKey] < usersActivityScore[b.userKey]) {
|
||||||
|
return activityscoreOrder === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (usersActivityScore[a.userKey] > usersActivityScore[b.userKey]) {
|
||||||
|
return activityscoreOrder === 'desc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (a.isModerator === false && b.isModerator === true) return 1;
|
||||||
|
if (a.isModerator === true && b.isModerator === false) return -1;
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b bg-gray-100">
|
<tr className="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b bg-gray-100">
|
||||||
<th className="px-3.5 2xl:px-4 py-3 col-text-left">
|
<th
|
||||||
|
className={`px-3.5 2xl:px-4 py-3 col-text-left ${tab === 'overview' ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={() => { if (tab === 'overview') this.toggleOrder('userOrder'); }}
|
||||||
|
>
|
||||||
<FormattedMessage id="app.learningDashboard.user" defaultMessage="User" />
|
<FormattedMessage id="app.learningDashboard.user" defaultMessage="User" />
|
||||||
{
|
{ tab === 'overview' && lastFieldClicked === 'userOrder'
|
||||||
tab === 'overview'
|
? renderArrow(userOrder)
|
||||||
? (
|
: null }
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="h-4 w-4 inline"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 13l-5 5m0 0l-5-5m5 5V6" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
<th
|
||||||
|
className={`px-3.5 2xl:px-4 py-3 text-center ${tab === 'overview' ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={() => { if (tab === 'overview') this.toggleOrder('onlineTimeOrder'); }}
|
||||||
|
>
|
||||||
<FormattedMessage id="app.learningDashboard.usersTable.colOnline" defaultMessage="Online time" />
|
<FormattedMessage id="app.learningDashboard.usersTable.colOnline" defaultMessage="Online time" />
|
||||||
|
{ tab === 'overview' && lastFieldClicked === 'onlineTimeOrder'
|
||||||
|
? renderArrow(onlineTimeOrder)
|
||||||
|
: null }
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
<th
|
||||||
|
className={`px-3.5 2xl:px-4 py-3 text-center ${tab === 'overview' ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={() => { if (tab === 'overview') this.toggleOrder('talkTimeOrder'); }}
|
||||||
|
>
|
||||||
<FormattedMessage id="app.learningDashboard.usersTable.colTalk" defaultMessage="Talk time" />
|
<FormattedMessage id="app.learningDashboard.usersTable.colTalk" defaultMessage="Talk time" />
|
||||||
|
{ tab === 'overview' && lastFieldClicked === 'talkTimeOrder'
|
||||||
|
? renderArrow(talkTimeOrder)
|
||||||
|
: null }
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
<th
|
||||||
|
className={`px-3.5 2xl:px-4 py-3 text-center ${tab === 'overview' ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={() => { if (tab === 'overview') this.toggleOrder('webcamTimeOrder'); }}
|
||||||
|
>
|
||||||
<FormattedMessage id="app.learningDashboard.usersTable.colWebcam" defaultMessage="Webcam Time" />
|
<FormattedMessage id="app.learningDashboard.usersTable.colWebcam" defaultMessage="Webcam Time" />
|
||||||
|
{ tab === 'overview' && lastFieldClicked === 'webcamTimeOrder'
|
||||||
|
? renderArrow(webcamTimeOrder)
|
||||||
|
: null }
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
||||||
<FormattedMessage id="app.learningDashboard.usersTable.colMessages" defaultMessage="Messages" />
|
<FormattedMessage id="app.learningDashboard.usersTable.colMessages" defaultMessage="Messages" />
|
||||||
@ -88,29 +202,12 @@ class UsersTable extends React.Component {
|
|||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className={`px-3.5 2xl:px-4 py-3 text-center ${tab === 'overview_activityscore' ? 'cursor-pointer' : ''}`}
|
className={`px-3.5 2xl:px-4 py-3 text-center ${tab === 'overview_activityscore' ? 'cursor-pointer' : ''}`}
|
||||||
onClick={() => { if (tab === 'overview_activityscore') this.toggleActivityScoreOrder(); }}
|
onClick={() => { if (tab === 'overview_activityscore') this.toggleOrder('activityscoreOrder'); }}
|
||||||
>
|
>
|
||||||
<FormattedMessage id="app.learningDashboard.usersTable.colActivityScore" defaultMessage="Activity Score" />
|
<FormattedMessage id="app.learningDashboard.usersTable.colActivityScore" defaultMessage="Activity Score" />
|
||||||
{
|
{ tab === 'overview_activityscore'
|
||||||
tab === 'overview_activityscore'
|
? renderArrow(activityscoreOrder)
|
||||||
? (
|
: null }
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="h-4 w-4 inline"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d={activityscoreOrder === 'asc' ? 'M17 13l-5 5m0 0l-5-5m5 5V6' : 'M7 11l5-5m0 0l5 5m-5-5v12'}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
<th className="px-3.5 2xl:px-4 py-3 text-center">
|
||||||
<FormattedMessage id="app.learningDashboard.usersTable.colStatus" defaultMessage="Status" />
|
<FormattedMessage id="app.learningDashboard.usersTable.colStatus" defaultMessage="Status" />
|
||||||
@ -120,19 +217,7 @@ class UsersTable extends React.Component {
|
|||||||
<tbody className="bg-white divide-y whitespace-nowrap">
|
<tbody className="bg-white divide-y whitespace-nowrap">
|
||||||
{ typeof allUsers === 'object' && Object.values(allUsers || {}).length > 0 ? (
|
{ typeof allUsers === 'object' && Object.values(allUsers || {}).length > 0 ? (
|
||||||
Object.values(allUsers || {})
|
Object.values(allUsers || {})
|
||||||
.sort((a, b) => {
|
.sort(tab === 'overview' ? sortFunctions[lastFieldClicked] : sortFunctions.activityscoreOrder)
|
||||||
if (tab === 'overview_activityscore' && usersActivityScore[a.userKey] < usersActivityScore[b.userKey]) {
|
|
||||||
return activityscoreOrder === 'desc' ? 1 : -1;
|
|
||||||
}
|
|
||||||
if (tab === 'overview_activityscore' && usersActivityScore[a.userKey] > usersActivityScore[b.userKey]) {
|
|
||||||
return activityscoreOrder === 'desc' ? -1 : 1;
|
|
||||||
}
|
|
||||||
if (a.isModerator === false && b.isModerator === true) return 1;
|
|
||||||
if (a.isModerator === true && b.isModerator === false) return -1;
|
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
.map((user) => {
|
.map((user) => {
|
||||||
const opacity = user.leftOn > 0 ? 'opacity-75' : '';
|
const opacity = user.leftOn > 0 ? 'opacity-75' : '';
|
||||||
return (
|
return (
|
||||||
@ -383,7 +468,11 @@ class UsersTable extends React.Component {
|
|||||||
<FormattedNumber value={usersActivityScore[user.userKey]} minimumFractionDigits="0" maximumFractionDigits="1" />
|
<FormattedNumber value={usersActivityScore[user.userKey]} minimumFractionDigits="0" maximumFractionDigits="1" />
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
) : <td />
|
) : (
|
||||||
|
<td className="px-4 py-3 text-sm text-center">
|
||||||
|
<FormattedMessage id="app.learningDashboard.usersTable.notAvailable" defaultMessage="N/A" />
|
||||||
|
</td>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
<td className="px-3.5 2xl:px-4 py-3 text-xs text-center">
|
<td className="px-3.5 2xl:px-4 py-3 text-xs text-center">
|
||||||
{
|
{
|
||||||
|
@ -985,7 +985,8 @@
|
|||||||
"app.learningDashboard.usersTable.pollVotes": "Poll Votes",
|
"app.learningDashboard.usersTable.pollVotes": "Poll Votes",
|
||||||
"app.learningDashboard.usersTable.join": "Join",
|
"app.learningDashboard.usersTable.join": "Join",
|
||||||
"app.learningDashboard.usersTable.left": "Left",
|
"app.learningDashboard.usersTable.left": "Left",
|
||||||
"app.learningDashboard.pollsTable.title": "Polling",
|
"app.learningDashboard.usersTable.notAvailable": "N/A",
|
||||||
|
"app.learningDashboard.pollsTable.title": "Polls",
|
||||||
"app.learningDashboard.pollsTable.anonymousAnswer": "Anonymous Poll (answers in the last row)",
|
"app.learningDashboard.pollsTable.anonymousAnswer": "Anonymous Poll (answers in the last row)",
|
||||||
"app.learningDashboard.pollsTable.anonymousRowName": "Anonymous",
|
"app.learningDashboard.pollsTable.anonymousRowName": "Anonymous",
|
||||||
"app.learningDashboard.pollsTable.noPollsCreatedHeading": "No polls have been created",
|
"app.learningDashboard.pollsTable.noPollsCreatedHeading": "No polls have been created",
|
||||||
|
Loading…
Reference in New Issue
Block a user