From 050138703767cbad2bb4f2db17e1f957f4ea10ea Mon Sep 17 00:00:00 2001 From: Zach Hancock Date: Mon, 17 Jul 2023 12:07:02 -0400 Subject: [PATCH] feat: finish component logic --- .../ExamsPage/components/ExamSelection.jsx | 47 ++++++++++++++----- src/pages/ExamsPage/data/reducer.js | 4 +- src/pages/ExamsPage/hooks.js | 10 +++- src/pages/ExamsPage/index.jsx | 21 +++------ src/pages/ExamsPage/index.scss | 14 ++++++ 5 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 src/pages/ExamsPage/index.scss diff --git a/src/pages/ExamsPage/components/ExamSelection.jsx b/src/pages/ExamsPage/components/ExamSelection.jsx index 5c2ae7f..fd7ba0c 100644 --- a/src/pages/ExamsPage/components/ExamSelection.jsx +++ b/src/pages/ExamsPage/components/ExamSelection.jsx @@ -1,32 +1,53 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { MenuItem, SearchField, SelectMenu, } from '@edx/paragon'; import PropTypes from 'prop-types'; -import { useUpdateCurrentExamIndex } from '../hooks'; -// todo: implement substring search +const ExamSelection = ({ exams, handleSelectExam }) => { + const { formatMessage, formatDate } = useIntl(); + const [searchText, setSearchText] = useState(''); -const ExamSelection = ({ exams }) => { - const { updateSelectedExam } = useUpdateCurrentExamIndex(); + const placeholderMessage = formatMessage({ + id: 'ExamSelection.select_exam_placeholder', + defaultMessage: 'Search for an exam...', + description: 'Placeholder message for the exam selection dropdown', + }); + + const getMenuItems = () => { + const menuItems = [ + , + ] + return menuItems.concat(exams.filter(exam => ( + exam.name.toLowerCase().includes(searchText.toLowerCase())) + ).map(exam => { + return handleSelectExam(exam.id)}>{exam.name} + })); + } return (
- - - {exams.map(exam => ( - // todo: update redux state - // console.log(exam.id)}>{exam.name} - updateSelectedExam(exam.id)}>{exam.name} - ))} + + { getMenuItems() }
); }; ExamSelection.propTypes = { - exams: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types + exams: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + })).isRequired, + handleSelectExam: PropTypes.func.isRequired, }; export default ExamSelection; diff --git a/src/pages/ExamsPage/data/reducer.js b/src/pages/ExamsPage/data/reducer.js index 3b83cbe..89e368c 100644 --- a/src/pages/ExamsPage/data/reducer.js +++ b/src/pages/ExamsPage/data/reducer.js @@ -36,7 +36,7 @@ const slice = createSlice({ ...state, attemptsList: state.attemptsList.filter(attempt => attempt.attempt_id !== attemptId.payload), }), - setCurrentExamIndex: (state, examId) => ({ + setCurrentExam: (state, examId) => ({ ...state, currentExamIndex: state.examsList.findIndex(exam => exam.id === examId.payload), }), @@ -47,7 +47,7 @@ export const { loadExams, loadExamAttempts, deleteExamAttempt, - setCurrentExamIndex, + setCurrentExam, } = slice.actions; export const { diff --git a/src/pages/ExamsPage/hooks.js b/src/pages/ExamsPage/hooks.js index f9bad45..8fbd2a1 100644 --- a/src/pages/ExamsPage/hooks.js +++ b/src/pages/ExamsPage/hooks.js @@ -57,15 +57,20 @@ export const useInitializeExamsPage = (courseId) => { React.useEffect(() => { fetchCourseExams(courseId); }, []); // eslint-disable-line react-hooks/exhaustive-deps }; -export const useUpdateCurrentExamIndex = () => { +export const useSetCurrentExam = () => { const dispatch = useDispatch(); - return (examId) => () => dispatch(reducer.setCurrentExamIndex(examId)); + const fetchExamAttempts = module.useFetchExamAttempts(); + return (examId) => { + dispatch(reducer.setCurrentExam(examId)); + fetchExamAttempts(examId); + }; }; export const useExamsData = () => { const [exampleValue, setExampleValue] = state.exampleValue(0); const examsList = useSelector(selectors.courseExamsList); const currentExam = useSelector(selectors.currentExam); + const setCurrentExam = module.useSetCurrentExam(); const isLoading = reduxHooks.useRequestIsPending(RequestKeys.fetchCourseExams); return { @@ -73,6 +78,7 @@ export const useExamsData = () => { examsList, isLoading, exampleValue, + setCurrentExam, setExampleValue, }; }; diff --git a/src/pages/ExamsPage/index.jsx b/src/pages/ExamsPage/index.jsx index e11bb35..1d830df 100644 --- a/src/pages/ExamsPage/index.jsx +++ b/src/pages/ExamsPage/index.jsx @@ -9,39 +9,30 @@ import { useExamsData, useInitializeExamsPage, useFetchExamAttempts, useExamAttemptsData, } from './hooks'; import AttemptList from './components/AttemptList'; -import ExamList from './components/ExamList'; import ExternalReviewDashboard from './components/ExternalReviewDashboard'; import ExamSelection from './components/ExamSelection'; +import './index.scss' + const ExamsPage = ({ courseId }) => { useInitializeExamsPage(courseId); const { formatMessage } = useIntl(); const { currentExam, examsList, - isLoading, + setCurrentExam, } = useExamsData(); const { attemptsList, } = useExamAttemptsData(); - /* eslint-disable react-hooks/exhaustive-deps */ - const fetchExamAttempts = useFetchExamAttempts(); - // NOTE: This useEffect hook is temporary until the currentExam is - // Passed in via the exam selection component - useEffect(() => { - if (currentExam) { - fetchExamAttempts(currentExam.id); - } - }, [currentExam]); - /* eslint-disable react-hooks/exhaustive-deps */ return ( - {isLoading &&
Loading...
} - + + + - diff --git a/src/pages/ExamsPage/index.scss b/src/pages/ExamsPage/index.scss new file mode 100644 index 0000000..e1a9e9b --- /dev/null +++ b/src/pages/ExamsPage/index.scss @@ -0,0 +1,14 @@ +@import "~@edx/paragon/scss/core/core"; + +#exam-selector { + margin: 0.5em auto; +} + +.pgn__searchfield .form-control { + border: 1px solid $search-border-color !important; +} + +.pgn__searchfield.has-focus:not(.pgn__searchfield--external)::after { + border: none; + width: 0px; +} \ No newline at end of file