Implements some updates on Learning Dashboard

This commit is contained in:
Joao Victor 2022-02-07 10:46:47 -03:00
parent 21db352ec6
commit 3e515b96e9
4 changed files with 155 additions and 64 deletions

View File

@ -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>
{ {

View File

@ -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"

View File

@ -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">
{ {

View File

@ -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",