-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BD-05] [BB-2963] Improve Instructor dashboard #2
base: master
Are you sure you want to change the base?
Changes from 2 commits
4e42535
c86d95f
3caf125
1707b68
2dc7bc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,34 @@ | ||
import React from 'react'; | ||
import { useSelector } from 'react-redux'; | ||
import { Spinner } from '@edx/paragon'; | ||
import SummaryTable from './summary-table/SummaryTable'; | ||
import DataTable from './data-table/DataTable'; | ||
import { oraDataShape } from '../data/constants'; | ||
import { oraDataShape, oraSummaryDataShape, RequestStatus } from '../data/constants'; | ||
import { loadingStatus } from './data/selectors'; | ||
|
||
function OraDashboard({ data, summary }) { | ||
const status = useSelector(loadingStatus); | ||
|
||
function OraDashboard({ data }) { | ||
return ( | ||
<main> | ||
<div className="container-fluid"> | ||
<h1>Open Responses</h1> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be translatable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now it's translatable. |
||
<SummaryTable /> | ||
<DataTable data={data} /> | ||
{status === RequestStatus.IN_PROGRESS && <Spinner animation="border" variant="primary" />} | ||
{status === RequestStatus.SUCCESSFUL && ( | ||
<div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this div isn't adding anything, you can remove it and just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for noticing that, yes a fragment would be better. |
||
<SummaryTable data={summary} /> | ||
<DataTable data={data} /> | ||
</div> | ||
)} | ||
|
||
</div> | ||
</main> | ||
); | ||
} | ||
|
||
OraDashboard.propTypes = { | ||
data: oraDataShape, | ||
}; | ||
|
||
OraDashboard.defaultProps = { | ||
data: {}, | ||
data: oraDataShape.isRequired, | ||
summary: oraSummaryDataShape.isRequired, | ||
}; | ||
|
||
export default OraDashboard; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,22 @@ | ||
import React, { useEffect } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { useParams } from 'react-router'; | ||
import { selectOraBlocks } from './data/selectors'; | ||
import { selectOraBlocks, selectSummary } from './data/selectors'; | ||
import { fetchOraBlocks } from './data/thunks'; | ||
import OraDashboard from './OraDashboard'; | ||
|
||
export default function OraDashboardContainer() { | ||
const { courseId } = useParams(); | ||
const dispatch = useDispatch(); | ||
const oraBlocks = useSelector(selectOraBlocks); | ||
const summary = useSelector(selectSummary); | ||
|
||
useEffect(() => { | ||
// The courseId from the URL is the course we WANT to load. | ||
dispatch(fetchOraBlocks(courseId)); | ||
}, [courseId]); | ||
|
||
return ( | ||
<OraDashboard data={oraBlocks} /> | ||
<OraDashboard data={oraBlocks} summary={summary} /> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,67 +1,106 @@ | ||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; | ||||||
import React, { useEffect } from 'react'; | ||||||
import { Table } from '@edx/paragon'; | ||||||
import { useDispatch } from 'react-redux'; | ||||||
import { useParams } from 'react-router'; | ||||||
import LoadingOrValue from '../../components/value-or-loading/ValueOrLoading'; | ||||||
import messages from './messages'; | ||||||
import { fetchOraReports } from '../data/thunks'; | ||||||
import { oraDataShape } from '../../data/constants'; | ||||||
|
||||||
/** | ||||||
* Sort implementation for Paragon Table | ||||||
* @param {any} firstElement | ||||||
* @param {any} secondElement | ||||||
* @param {string} key | ||||||
* @param {string} direction | ||||||
*/ | ||||||
const sort = function sort(firstElement, secondElement, key, direction) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think naming the function twice is a bit redundant.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also this seems like a utility function, so can probably moved to a common places and imported as such. |
||||||
const directionIsAsc = direction === 'asc'; | ||||||
|
||||||
if (firstElement[key] > secondElement[key]) { | ||||||
return directionIsAsc ? 1 : -1; | ||||||
} if (firstElement[key] < secondElement[key]) { | ||||||
return directionIsAsc ? -1 : 1; | ||||||
} | ||||||
return 0; | ||||||
}; | ||||||
|
||||||
function DataTable({ intl, data }) { | ||||||
const { courseId } = useParams(); | ||||||
const dispatch = useDispatch(); | ||||||
useEffect(() => { | ||||||
Object.keys(data).map((blockId) => data[blockId].status || dispatch(fetchOraReports(courseId, blockId))); | ||||||
if (Object.keys(data).length > 0) { | ||||||
const blockId = Object.keys(data)[0]; | ||||||
if (!data[blockId].status) { | ||||||
dispatch(fetchOraReports(courseId, blockId)); | ||||||
} | ||||||
} | ||||||
}, [courseId, data]); | ||||||
|
||||||
// create a copy of data for sortable Table | ||||||
const sortableData = Object.values(data).slice(); | ||||||
|
||||||
// define Table columns | ||||||
const columns = [ | ||||||
{ | ||||||
label: intl.formatMessage(messages.unit_name), | ||||||
key: 'vertical', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.assessment), | ||||||
key: 'name', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.total_responses), | ||||||
key: 'total', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.training), | ||||||
key: 'training', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.peer), | ||||||
key: 'peer', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.self), | ||||||
key: 'self', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.waiting), | ||||||
key: 'waiting', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.staff), | ||||||
key: 'staff', | ||||||
columnSortable: true, | ||||||
}, | ||||||
{ | ||||||
label: intl.formatMessage(messages.final_grade_received), | ||||||
key: 'done', | ||||||
columnSortable: true, | ||||||
}, | ||||||
]; | ||||||
|
||||||
return ( | ||||||
<table className="table"> | ||||||
<thead> | ||||||
<tr> | ||||||
<th> | ||||||
{intl.formatMessage(messages.unit_name)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.assessment)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.total_responses)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.training)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.peer)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.self)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.waiting)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.staff)} | ||||||
</th> | ||||||
<th> | ||||||
{intl.formatMessage(messages.final_grade_received)} | ||||||
</th> | ||||||
</tr> | ||||||
</thead> | ||||||
<tbody> | ||||||
{Object.values(data).map(((block) => ( | ||||||
<tr key={block.id}> | ||||||
<td>{block.vertical}</td> | ||||||
<td>{block.name}</td> | ||||||
<td><LoadingOrValue value={block.total} /></td> | ||||||
<td><LoadingOrValue value={block.training} /></td> | ||||||
<td><LoadingOrValue value={block.peer} /></td> | ||||||
<td><LoadingOrValue value={block.self} /></td> | ||||||
<td><LoadingOrValue value={block.waiting} /></td> | ||||||
<td><LoadingOrValue value={block.staff} /></td> | ||||||
<td><LoadingOrValue value={block.done} /></td> | ||||||
</tr> | ||||||
)))} | ||||||
</tbody> | ||||||
</table> | ||||||
<Table | ||||||
data={sortableData} | ||||||
columns={columns.map(column => ({ | ||||||
...column, | ||||||
onSort(direction) { | ||||||
console.log('Sort in direction ', direction, column); | ||||||
sortableData.sort((firstElement, secondElement) => sort(firstElement, secondElement, column.key, direction)); | ||||||
}, | ||||||
}))} | ||||||
tableSortable | ||||||
/> | ||||||
); | ||||||
} | ||||||
DataTable.propTypes = { | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
export const selectOraBlocks = state => state.blocks; | ||
|
||
export const selectSummary = state => state.summary; | ||
|
||
export const loadingStatus = state => state.status; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having it be a node means it can be anything that can be rendered, so it will match, numbers, strings and other react elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.