generated from openedx/frontend-template-application
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from Pearson-Advance/vue/PADV-966
PADV-966 feat: Add assign instructors modal
- Loading branch information
Showing
16 changed files
with
567 additions
and
33 deletions.
There are no files selected for viewing
37 changes: 28 additions & 9 deletions
37
src/features/Dashboard/InstructorAssignSection/ClassCard.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
src/features/Instructors/AssignInstructors/AssignTable.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React, { useMemo } from 'react'; | ||
import { useSelector } from 'react-redux'; | ||
|
||
import { IntlProvider } from 'react-intl'; | ||
import { | ||
Row, | ||
Col, | ||
DataTable, | ||
} from '@edx/paragon'; | ||
import ControlledSelect from 'features/Instructors/AssignInstructors/ControlledSelect'; | ||
|
||
import { columns } from 'features/Instructors/AssignInstructors/columns'; | ||
|
||
const AssignTable = () => { | ||
const stateInstructors = useSelector((state) => state.instructors.table); | ||
const COLUMNS = useMemo(() => columns(), []); | ||
|
||
const selectColumn = { | ||
id: 'selection', | ||
Header: <></>, // eslint-disable-line react/jsx-no-useless-fragment | ||
Cell: ControlledSelect, | ||
disableSortBy: true, | ||
}; | ||
|
||
return ( | ||
<IntlProvider locale="en"> | ||
<Row className="justify-content-center my-4 my-3"> | ||
<Col xs={11} className="p-0"> | ||
<DataTable | ||
isSortable | ||
isSelectable | ||
columns={COLUMNS} | ||
itemCount={stateInstructors.count} | ||
data={stateInstructors.data} | ||
manualSelectColumn={selectColumn} | ||
initialTableOptions={{ | ||
autoResetSelectedRows: false, | ||
getRowId: (row) => row.instructorUsername, | ||
}} | ||
> | ||
<DataTable.Table /> | ||
<DataTable.EmptyTable content="No instructors found." /> | ||
</DataTable> | ||
</Col> | ||
</Row> | ||
</IntlProvider> | ||
); | ||
}; | ||
|
||
export default AssignTable; |
58 changes: 58 additions & 0 deletions
58
src/features/Instructors/AssignInstructors/ControlledSelect.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import React, { | ||
useCallback, useMemo, | ||
} from 'react'; | ||
import { useDispatch } from 'react-redux'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { CheckboxControl } from '@edx/paragon'; | ||
import { addRowSelect, deleteRowSelect } from 'features/Instructors/data/slice'; | ||
|
||
const useConvertIndeterminateProp = (props) => { | ||
const updatedProps = useMemo( | ||
() => { | ||
const { indeterminate, ...rest } = props; | ||
return { isIndeterminate: indeterminate, ...rest }; | ||
}, | ||
[props], | ||
); | ||
return updatedProps; | ||
}; | ||
|
||
const ControlledSelect = ({ row }) => { | ||
const dispatch = useDispatch(); | ||
|
||
const toggleSelected = useCallback( | ||
() => { | ||
if (row.isSelected) { | ||
dispatch(deleteRowSelect(row.id)); | ||
row.toggleRowSelected(); | ||
} else { | ||
dispatch(addRowSelect(row.id)); | ||
row.toggleRowSelected(); | ||
} | ||
}, | ||
[row, dispatch], | ||
); | ||
|
||
const updatedProps = useConvertIndeterminateProp(row.getToggleRowSelectedProps()); | ||
|
||
return ( | ||
<div className="test-checkbox"> | ||
<CheckboxControl | ||
{...updatedProps} | ||
onChange={toggleSelected} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
ControlledSelect.propTypes = { | ||
row: PropTypes.shape({ | ||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, | ||
getToggleRowSelectedProps: PropTypes.func.isRequired, | ||
isSelected: PropTypes.bool.isRequired, | ||
toggleRowSelected: PropTypes.func.isRequired, | ||
}).isRequired, | ||
}; | ||
|
||
export default ControlledSelect; |
74 changes: 74 additions & 0 deletions
74
src/features/Instructors/AssignInstructors/_test_/index.test.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from 'react'; | ||
import { waitFor, fireEvent } from '@testing-library/react'; | ||
import AssignInstructors from 'features/Instructors/AssignInstructors'; | ||
import '@testing-library/jest-dom/extend-expect'; | ||
import { renderWithProviders } from 'test-utils'; | ||
import { RequestStatus } from 'features/constants'; | ||
|
||
jest.mock('@edx/frontend-platform/logging', () => ({ | ||
logError: jest.fn(), | ||
})); | ||
|
||
const mockStore = { | ||
instructors: { | ||
table: { | ||
data: [ | ||
{ | ||
instructorUsername: 'Instructor1', | ||
instructorName: 'Instructor 1', | ||
instructorEmail: '[email protected]', | ||
ccxId: 'CCX1', | ||
ccxName: 'CCX 1', | ||
}, | ||
{ | ||
instructorUsername: 'Instructor2', | ||
instructorName: 'Instructor 2', | ||
instructorEmail: '[email protected]', | ||
ccxId: 'CCX2', | ||
ccxName: 'CCX 2', | ||
}, | ||
], | ||
count: 2, | ||
num_pages: 1, | ||
current_page: 1, | ||
}, | ||
classes: { | ||
data: [], | ||
}, | ||
courses: { | ||
data: [], | ||
}, | ||
filters: { | ||
}, | ||
rowsSelected: [], | ||
classSelected: '', | ||
assignInstructors: { | ||
status: RequestStatus.LOADING, | ||
error: null, | ||
data: [], | ||
}, | ||
}, | ||
}; | ||
|
||
describe('Assign instructors modal', () => { | ||
test('render assing instructors modal', () => { | ||
const { getByText, getAllByRole, getByTestId } = renderWithProviders( | ||
<AssignInstructors isOpen close={() => {}} />, | ||
{ preloadedState: mockStore }, | ||
); | ||
|
||
waitFor(() => { | ||
expect(getByText('Instructor')).toBeInTheDocument(); | ||
expect(getByText('Instructor1')).toBeInTheDocument(); | ||
expect(getByText('Instructor2')).toBeInTheDocument(); | ||
expect(getByText('Last seen')).toBeInTheDocument(); | ||
expect(getByText('Courses taught')).toBeInTheDocument(); | ||
}); | ||
|
||
const checkboxFields = getAllByRole('checkbox'); | ||
fireEvent.click(checkboxFields[0]); | ||
|
||
const assignButton = getByTestId('assignButton'); | ||
fireEvent.click(assignButton); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable react/prop-types, no-nested-ternary */ | ||
import { differenceInHours, differenceInDays, differenceInWeeks } from 'date-fns'; | ||
|
||
import { daysWeek, hoursDay } from 'features/constants'; | ||
|
||
const columns = () => [ | ||
{ | ||
Header: 'Instructor', | ||
accessor: 'instructorName', | ||
}, | ||
{ | ||
Header: 'Last seen', | ||
accessor: 'lastAccess', | ||
Cell: ({ row }) => { | ||
const currentDate = Date.now(); | ||
const lastDate = new Date(row.values.lastAccess); | ||
const diffHours = differenceInHours(currentDate, lastDate); | ||
const diffDays = differenceInDays(currentDate, lastDate); | ||
const diffWeeks = differenceInWeeks(currentDate, lastDate); | ||
return ( | ||
<span>{diffHours < hoursDay | ||
? 'Today' | ||
: diffDays < daysWeek | ||
? `${diffDays} days ago` | ||
: `${diffWeeks} wks ago`} | ||
</span> | ||
); | ||
}, | ||
}, | ||
{ | ||
Header: 'Courses Taught', | ||
accessor: 'classes', | ||
disableSortBy: true, | ||
}, | ||
]; | ||
|
||
export { columns }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { ModalDialog, ModalCloseButton, Pagination } from '@edx/paragon'; | ||
import { Button } from 'react-paragon-topaz'; | ||
import InstructorsFilters from 'features/Instructors/InstructorsFilters'; | ||
import AssignTable from 'features/Instructors/AssignInstructors/AssignTable'; | ||
|
||
import { fetchInstructorsData, assignInstructors } from 'features/Instructors/data'; | ||
import { | ||
updateCurrentPage, updateFilters, updateClassSelected, | ||
} from 'features/Instructors/data/slice'; | ||
|
||
import { initialPage } from 'features/constants'; | ||
import 'features/Instructors/AssignInstructors/index.scss'; | ||
|
||
const AssignInstructors = ({ isOpen, close }) => { | ||
const dispatch = useDispatch(); | ||
const selectedInstitution = useSelector((state) => state.main.selectedInstitution); | ||
const stateInstructors = useSelector((state) => state.instructors); | ||
const rowsSelected = useSelector((state) => state.instructors.rowsSelected); | ||
const classId = useSelector((state) => state.instructors.classSelected); | ||
const [currentPage, setCurrentPage] = useState(initialPage); | ||
|
||
const resetPagination = () => { | ||
setCurrentPage(initialPage); | ||
}; | ||
|
||
const handlePagination = (targetPage) => { | ||
setCurrentPage(targetPage); | ||
dispatch(updateCurrentPage(targetPage)); | ||
}; | ||
|
||
const handleAssignInstructors = async () => { | ||
// eslint-disable-next-line array-callback-return | ||
rowsSelected.map(row => { | ||
const enrollmentData = new FormData(); | ||
enrollmentData.append('unique_student_identifier', row); | ||
enrollmentData.append('rolename', 'staff'); | ||
enrollmentData.append('action', 'allow'); | ||
dispatch(assignInstructors(enrollmentData, classId, selectedInstitution.id)); | ||
}); | ||
close(); | ||
}; | ||
|
||
useEffect(() => { | ||
if (Object.keys(selectedInstitution).length > 0) { | ||
dispatch(fetchInstructorsData(selectedInstitution.id, currentPage, stateInstructors.filters)); | ||
} | ||
}, [currentPage, selectedInstitution, dispatch]); // eslint-disable-line react-hooks/exhaustive-deps | ||
|
||
useEffect(() => { | ||
if (!isOpen) { | ||
dispatch(updateFilters({})); | ||
dispatch(updateClassSelected('')); | ||
} | ||
}, [isOpen, dispatch]); | ||
|
||
return ( | ||
<ModalDialog | ||
title="Assign instructor" | ||
isOpen={isOpen} | ||
onClose={close} | ||
hasCloseButton | ||
size="lg" | ||
> | ||
<ModalDialog.Header> | ||
<ModalDialog.Title> | ||
Assign instructor | ||
</ModalDialog.Title> | ||
</ModalDialog.Header> | ||
<ModalDialog.Body> | ||
<InstructorsFilters isAssignModal resetPagination={resetPagination} /> | ||
<AssignTable /> | ||
{stateInstructors.table.numPages > 1 && ( | ||
<Pagination | ||
paginationLabel="paginationNavigation" | ||
pageCount={stateInstructors.table.numPages} | ||
currentPage={currentPage} | ||
onPageSelect={handlePagination} | ||
variant="reduced" | ||
className="mx-auto pagination-table" | ||
size="small" | ||
/> | ||
)} | ||
<div className="d-flex justify-content-end"> | ||
<ModalCloseButton className="btntpz btn-text btn-tertiary">Cancel</ModalCloseButton> | ||
<Button onClick={handleAssignInstructors} data-testid="assignButton">Assign instructor</Button> | ||
</div> | ||
</ModalDialog.Body> | ||
</ModalDialog> | ||
); | ||
}; | ||
|
||
AssignInstructors.propTypes = { | ||
isOpen: PropTypes.bool.isRequired, | ||
close: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default AssignInstructors; |
Oops, something went wrong.