From 6eb7f849324c1918914cf00ae5715026c0cc4f5b Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Tue, 15 Feb 2022 19:13:55 +0100 Subject: [PATCH] Adding icon that will allow the user hide everyone else in the results table, so one can better read his/her own points. --- .../Groups/ResultsTable/ResultsTable.js | 299 +++++++++++------- .../widgets/SortableTable/SortableTable.js | 2 +- src/containers/App/recodex.css | 24 +- 3 files changed, 198 insertions(+), 127 deletions(-) diff --git a/src/components/Groups/ResultsTable/ResultsTable.js b/src/components/Groups/ResultsTable/ResultsTable.js index e07289163..9f0864fe6 100644 --- a/src/components/Groups/ResultsTable/ResultsTable.js +++ b/src/components/Groups/ResultsTable/ResultsTable.js @@ -22,9 +22,10 @@ import { createUserNameComparator } from '../../helpers/users'; import { compareAssignments, compareShadowAssignments } from '../../helpers/assignments'; import { downloadString } from '../../../redux/helpers/api/download'; import Button from '../../widgets/TheButton'; -import { DownloadIcon, LoadingIcon } from '../../icons'; +import Icon, { DownloadIcon, LoadingIcon } from '../../icons'; import { safeGet, EMPTY_ARRAY, EMPTY_OBJ, hasPermissions } from '../../../helpers/common'; import withLinks from '../../../helpers/withLinks'; +import { storageGetItem, storageSetItem } from '../../../helpers/localStorage'; import styles from './ResultsTable.less'; import escapeString from '../../helpers/escapeString'; @@ -126,6 +127,8 @@ const getCSVValues = (assignments, shadowAssignments, data, locale) => { return result.map(row => row.join(SEPARATOR)).join(NEWLINE); }; +const localStorageShowOnlyMeKey = 'ResultsTable.showOnlyMe'; + class ResultsTable extends Component { state = { dialogOpen: false, @@ -133,6 +136,17 @@ class ResultsTable extends Component { dialogUserId: null, dialogAssignmentId: null, dialogShadowId: null, + showOnlyMe: false, + }; + + componentDidMount = () => { + this.setState({ showOnlyMe: storageGetItem(localStorageShowOnlyMeKey, false) }); + }; + + toggleShowOnlyMe = () => { + const showOnlyMe = !this.state.showOnlyMe; + storageSetItem(localStorageShowOnlyMeKey, showOnlyMe); + this.setState({ showOnlyMe }); }; openDialogAssignment = (dialogUserId, dialogAssignmentId) => { @@ -204,144 +218,180 @@ class ResultsTable extends Component { } }; - prepareColumnDescriptors = defaultMemoize((assignments, shadowAssignments, loggedUser, locale, isTeacher) => { - const { - group, - links: { - ASSIGNMENT_STATS_URI_FACTORY, - ASSIGNMENT_DETAIL_URI_FACTORY, - SHADOW_ASSIGNMENT_DETAIL_URI_FACTORY, - GROUP_USER_SOLUTIONS_URI_FACTORY, - }, - } = this.props; - - const nameComparator = createUserNameComparator(locale); - - /* - * User Name (First Column) - */ - const columns = [ - new SortableTableColumnDescriptor('user', , { - headerSuffix: , - headerSuffixClassName: styles.maxPointsRow, - className: 'text-left', - comparator: ({ user: u1 }, { user: u2 }) => nameComparator(u1, u2), - cellRenderer: user => - user && ( - - ), - }), - ]; - - /* - * Assignments - */ - assignments.sort(compareAssignments).forEach(assignment => - columns.push( + prepareColumnDescriptors = defaultMemoize( + (assignments, shadowAssignments, loggedUser, locale, isTeacher, showOnlyMe = false) => { + const { + group, + links: { + ASSIGNMENT_STATS_URI_FACTORY, + ASSIGNMENT_DETAIL_URI_FACTORY, + SHADOW_ASSIGNMENT_DETAIL_URI_FACTORY, + GROUP_USER_SOLUTIONS_URI_FACTORY, + }, + } = this.props; + + const nameComparator = createUserNameComparator(locale); + + /* + * User Name (First Column) + */ + const columns = [ new SortableTableColumnDescriptor( - assignment.id, - ( -
-
- - - -
-
- ), + 'user', + , { - headerClassName: 'text-center', - className: 'text-center', - headerSuffix: - assignment.maxPointsBeforeFirstDeadline + - (assignment.maxPointsBeforeSecondDeadline ? ` / ${assignment.maxPointsBeforeSecondDeadline}` : ''), + headerSuffix: , headerSuffixClassName: styles.maxPointsRow, - cellRenderer: assignmentCellRendererCreator(assignments, locale), - isClickable: hasPermissions(assignment, 'viewAssignmentSolutions') - ? true - : (userId, _) => userId === loggedUser.id, - onClick: hasPermissions(assignment, 'viewAssignmentSolutions') - ? (userId, assignmentId) => this.openDialogAssignment(userId, assignmentId) - : (userId, assignmentId) => userId === loggedUser.id && this.openDialogAssignment(userId, assignmentId), + className: 'text-left', + comparator: showOnlyMe ? null : ({ user: u1 }, { user: u2 }) => nameComparator(u1, u2), + cellRenderer: user => + user && ( + <> + + {!isTeacher && user.id === loggedUser.id && hasPermissions(group, 'viewStats') && ( + + {showOnlyMe ? ( + + ) : ( + + )} + + }> + + + )} + + ), } + ), + ]; + + /* + * Assignments + */ + assignments.sort(compareAssignments).forEach(assignment => + columns.push( + new SortableTableColumnDescriptor( + assignment.id, + ( +
+
+ + + +
+
+ ), + { + headerClassName: 'text-center', + className: 'text-center', + headerSuffix: + assignment.maxPointsBeforeFirstDeadline + + (assignment.maxPointsBeforeSecondDeadline ? ` / ${assignment.maxPointsBeforeSecondDeadline}` : ''), + headerSuffixClassName: styles.maxPointsRow, + cellRenderer: assignmentCellRendererCreator(assignments, locale), + isClickable: hasPermissions(assignment, 'viewAssignmentSolutions') + ? true + : (userId, _) => userId === loggedUser.id, + onClick: hasPermissions(assignment, 'viewAssignmentSolutions') + ? (userId, assignmentId) => this.openDialogAssignment(userId, assignmentId) + : (userId, assignmentId) => userId === loggedUser.id && this.openDialogAssignment(userId, assignmentId), + } + ) ) - ) - ); + ); + + /* + * Shadow Assignments + */ + shadowAssignments.sort(compareShadowAssignments).forEach(shadowAssignment => + columns.push( + new SortableTableColumnDescriptor( + shadowAssignment.id, + ( +
+
+ + + +
+
+ ), + { + className: 'text-center', + headerSuffix: shadowAssignment.maxPoints, + headerSuffixClassName: styles.maxPointsRow, + cellRenderer: shadowAssignmentCellRendererCreator(shadowAssignments, locale), + isClickable: true, + onClick: (userId, shadowId) => this.openDialogShadowAssignment(userId, shadowId), + } + ) + ) + ); - /* - * Shadow Assignments - */ - shadowAssignments.sort(compareShadowAssignments).forEach(shadowAssignment => + /* + * Total points and optionally buttons + */ columns.push( new SortableTableColumnDescriptor( - shadowAssignment.id, - ( -
-
- - - -
-
- ), + 'total', + , { className: 'text-center', - headerSuffix: shadowAssignment.maxPoints, headerSuffixClassName: styles.maxPointsRow, - cellRenderer: shadowAssignmentCellRendererCreator(shadowAssignments, locale), - isClickable: true, - onClick: (userId, shadowId) => this.openDialogShadowAssignment(userId, shadowId), + comparator: ({ total: t1, user: u1 }, { total: t2, user: u2 }) => + (Number(t2 && t2.gained) || -1) - (Number(t1 && t1.gained) || -1) || nameComparator(u1, u2), + cellRenderer: points => {points ? `${points.gained}/${points.total}` : '-/-'}, } ) - ) - ); + ); - /* - * Total points and optionally buttons - */ - columns.push( - new SortableTableColumnDescriptor( - 'total', - , - { - className: 'text-center', - headerSuffixClassName: styles.maxPointsRow, - comparator: ({ total: t1, user: u1 }, { total: t2, user: u2 }) => - (Number(t2 && t2.gained) || -1) - (Number(t1 && t1.gained) || -1) || nameComparator(u1, u2), - cellRenderer: points => {points ? `${points.gained}/${points.total}` : '-/-'}, - } - ) - ); + if (hasPermissions(group, 'update')) { + columns.push( + new SortableTableColumnDescriptor('buttons', '', { + headerSuffixClassName: styles.maxPointsRow, + className: 'text-right', + }) + ); + } - if (hasPermissions(group, 'update')) { - columns.push( - new SortableTableColumnDescriptor('buttons', '', { - headerSuffixClassName: styles.maxPointsRow, - className: 'text-right', - }) - ); + return columns; } - - return columns; - }); + ); // Re-format the data, so they can be rendered by the SortableTable ... - prepareData = defaultMemoize((assignments, shadowAssignments, users, stats, showButtons) => { + prepareData = defaultMemoize((assignments, shadowAssignments, users, stats, showOnlyMe = false) => { const { loggedUser, renderActions, group } = this.props; - if (!hasPermissions(group, 'viewStats')) { + if (!hasPermissions(group, 'viewStats') || showOnlyMe) { users = users.filter(({ id }) => id === loggedUser.id); } @@ -349,6 +399,7 @@ class ResultsTable extends Component { const userStats = stats.find(stat => stat.userId === user.id); const data = { id: user.id, + selected: !showOnlyMe && user.id === loggedUser.id, user: user, total: userStats && userStats.points, buttons: renderActions && hasPermissions(group, 'update') ? renderActions(user.id) : '', @@ -400,10 +451,11 @@ class ResultsTable extends Component { shadowAssignments, loggedUser, locale, - isAdmin || isSupervisor || isObserver + isAdmin || isSupervisor || isObserver, + this.state.showOnlyMe )} defaultOrder="user" - data={this.prepareData(assignments, shadowAssignments, users, stats)} + data={this.prepareData(assignments, shadowAssignments, users, stats, this.state.showOnlyMe)} empty={
} + className="hover mb-0" /> {(isAdmin || isSupervisor || isObserver) && (
@@ -424,7 +477,7 @@ class ResultsTable extends Component { getCSVValues( assignments, shadowAssignments, - this.prepareData(assignments, shadowAssignments, users, stats), + this.prepareData(assignments, shadowAssignments, users, stats, this.state.showOnlyMe), locale ), 'text/csv;charset=utf-8', diff --git a/src/components/widgets/SortableTable/SortableTable.js b/src/components/widgets/SortableTable/SortableTable.js index cb7cc5cf6..02b987244 100644 --- a/src/components/widgets/SortableTable/SortableTable.js +++ b/src/components/widgets/SortableTable/SortableTable.js @@ -13,7 +13,7 @@ class SortableTable extends Component { // Default row rendering fucntion (if the user does not provide custom function) defaultRowRenderer = (row, idx, columns) => ( - + {columns.map(({ id: colId, cellRenderer, style, className, onClick, isClickable }) => { if (typeof isClickable === 'function') { isClickable = isClickable(row.id, colId); diff --git a/src/containers/App/recodex.css b/src/containers/App/recodex.css index 4b6dec0df..ca40716c4 100644 --- a/src/containers/App/recodex.css +++ b/src/containers/App/recodex.css @@ -209,14 +209,32 @@ th.shrink-col { cursor: pointer; } -table.table-hover td.clickable:hover, table.table-hover th.clickable:hover { +table.table-hover td.clickable:hover, +table.table-hover th.clickable:hover, +table.table.tbody-hover tbody:hover td, +table.table.tbody-hover tbody:hover th { background-color: #eeeeee; } -table.table.tbody-hover tbody:hover td { - background-color: #eeeeee; +table .selected td, +table .selected th, +table td.selected, +table th.selected { + background-color: #bfb; +} + +table.table-hover .selected td.clickable:hover, +table.table-hover .selected th.clickable:hover, +table.table-hover td.clickable.selected:hover, +table.table-hover th.clickable.selected:hover, +table.table.tbody-hover tbody:hover .selected td, +table.table.tbody-hover tbody:hover .selected th, +table.table.tbody-hover tbody:hover td.selected, +table.table.tbody-hover tbody:hover th.selected { + background-color: #b0ddb0; } + .timid { opacity: 0.2; transition: opacity 0.5s ease;