From 75b58c89b2c90e96add8d0f47d5e96683d544e16 Mon Sep 17 00:00:00 2001 From: diana-villalvazo-wgu Date: Fri, 28 Nov 2025 13:14:15 -0500 Subject: [PATCH 01/12] feat: graded subsection filter --- src/dateExtensions/DateExtensionsPage.tsx | 8 +++++++- src/dateExtensions/messages.ts | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/dateExtensions/DateExtensionsPage.tsx b/src/dateExtensions/DateExtensionsPage.tsx index 35e7d44c..18a38de9 100644 --- a/src/dateExtensions/DateExtensionsPage.tsx +++ b/src/dateExtensions/DateExtensionsPage.tsx @@ -10,6 +10,7 @@ import { useAddDateExtensionMutation, useResetDateExtensionMutation } from './da import { useAlert } from '@src/providers/AlertProvider'; import AddExtensionModal from './components/AddExtensionModal'; import { APIError } from '@src/types'; +import SelectGradedSubsection from './components/SelectGradedSubsection'; const DateExtensionsPage = () => { const intl = useIntl(); @@ -85,7 +86,12 @@ const DateExtensionsPage = () => {

{intl.formatMessage(messages.dateExtensionsTitle)}

-

filters

+
+ {}} + /> +
diff --git a/src/dateExtensions/messages.ts b/src/dateExtensions/messages.ts index ffed0011..3ea5ce2b 100644 --- a/src/dateExtensions/messages.ts +++ b/src/dateExtensions/messages.ts @@ -111,6 +111,11 @@ const messages = defineMessages({ defaultMessage: 'Select Graded Subsection', description: 'Label for the select graded subsection field', }, + allGradedSubsections: { + id: 'instruct.dateExtensions.page.filters.allGradedSubsections', + defaultMessage: 'All Graded Subsections', + description: 'Label for the all graded subsections option in filters', + }, }); export default messages; From 1fd2580559902e752fd7093c97da244dcc0d5fd7 Mon Sep 17 00:00:00 2001 From: diana-villalvazo-wgu Date: Fri, 28 Nov 2025 14:04:53 -0500 Subject: [PATCH 02/12] feat: search field --- .../DateExtensionsPage.test.tsx | 19 +++++++++++++-- src/dateExtensions/DateExtensionsPage.tsx | 22 ++++++++++++++---- .../components/DateExtensionsList.tsx | 10 ++++++-- .../components/SelectGradedSubsection.tsx | 13 +++++++++-- src/dateExtensions/data/api.ts | 23 +++++++++++++++++-- src/dateExtensions/data/apiHook.ts | 10 ++++---- src/dateExtensions/messages.ts | 5 ++++ 7 files changed, 85 insertions(+), 17 deletions(-) diff --git a/src/dateExtensions/DateExtensionsPage.test.tsx b/src/dateExtensions/DateExtensionsPage.test.tsx index 009a6858..9ad910e0 100644 --- a/src/dateExtensions/DateExtensionsPage.test.tsx +++ b/src/dateExtensions/DateExtensionsPage.test.tsx @@ -1,7 +1,7 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import DateExtensionsPage from './DateExtensionsPage'; -import { useDateExtensions, useResetDateExtensionMutation } from './data/apiHook'; +import { useDateExtensions, useGradedSubsections, useAddDateExtensionMutation, useResetDateExtensionMutation } from './data/apiHook'; import { renderWithAlertAndIntl } from '@src/testUtils'; jest.mock('react-router-dom', () => ({ @@ -15,6 +15,7 @@ jest.mock('./data/apiHook', () => ({ useDateExtensions: jest.fn(), useResetDateExtensionMutation: jest.fn(), useAddDateExtensionMutation: jest.fn(() => ({ mutate: jest.fn() })), + useGradedSubsections: jest.fn(), })); const mockDateExtensions = [ @@ -28,6 +29,13 @@ const mockDateExtensions = [ }, ]; +const mockGradedSubsections = [ + { + subsectionId: 'subsection-1block-v1:edX+DemoX+2015+type@problem+block@618c5933b8b544e4a4cc103d3e508378', + displayName: 'Three body diagrams' + } +]; + const mutateMock = jest.fn(); describe('DateExtensionsPage', () => { @@ -39,6 +47,13 @@ describe('DateExtensionsPage', () => { (useResetDateExtensionMutation as jest.Mock).mockReturnValue({ mutate: mutateMock, }); + (useAddDateExtensionMutation as jest.Mock).mockReturnValue({ + mutate: jest.fn(), + }); + (useGradedSubsections as jest.Mock).mockReturnValue({ + data: { items: mockGradedSubsections }, + isLoading: false, + }); }); it('renders page title', () => { @@ -54,7 +69,7 @@ describe('DateExtensionsPage', () => { it('renders date extensions list', () => { renderWithAlertAndIntl(); expect(screen.getByText('Ed Byun')).toBeInTheDocument(); - expect(screen.getByText('Three body diagrams')).toBeInTheDocument(); + expect(screen.getByRole('cell', { name: 'Three body diagrams' })).toBeInTheDocument(); }); it('shows loading state on table when fetching data', () => { diff --git a/src/dateExtensions/DateExtensionsPage.tsx b/src/dateExtensions/DateExtensionsPage.tsx index 18a38de9..6380d4bc 100644 --- a/src/dateExtensions/DateExtensionsPage.tsx +++ b/src/dateExtensions/DateExtensionsPage.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { useIntl } from '@openedx/frontend-base'; -import { Button } from '@openedx/paragon'; +import { Button, FormControl, Icon } from '@openedx/paragon'; import messages from './messages'; import DateExtensionsList from './components/DateExtensionsList'; import ResetExtensionsModal from './components/ResetExtensionsModal'; @@ -11,6 +11,7 @@ import { useAlert } from '@src/providers/AlertProvider'; import AddExtensionModal from './components/AddExtensionModal'; import { APIError } from '@src/types'; import SelectGradedSubsection from './components/SelectGradedSubsection'; +import { Search } from '@openedx/paragon/icons'; const DateExtensionsPage = () => { const intl = useIntl(); @@ -21,6 +22,8 @@ const DateExtensionsPage = () => { const isResetModalOpen = selectedUser !== null; const { showToast, showModal, removeAlert, clearAlerts } = useAlert(); const [isAddExtensionModalOpen, setIsAddExtensionModalOpen] = useState(false); + const [searchedLearner, setSearchedLearner] = useState(''); + const [gradedSubsectionFilter, setGradedSubsectionFilter] = useState(''); const handleResetExtensions = (user: LearnerDateExtension) => { clearAlerts(); @@ -86,15 +89,26 @@ const DateExtensionsPage = () => {

{intl.formatMessage(messages.dateExtensionsTitle)}

-
+
+ setSearchedLearner(e.target.value)} + placeholder={intl.formatMessage(messages.searchLearnerPlaceholder)} + trailingElement={} + value={searchedLearner} + /> {}} + onChange={(e) => setGradedSubsectionFilter(e.target.value)} + value={gradedSubsectionFilter} />
- + void, + emailOrUsername?: string, + blockId?: string, } interface DataTableFetchDataProps { @@ -18,13 +20,17 @@ interface DataTableFetchDataProps { const DateExtensionsList = ({ onResetExtensions = () => {}, + emailOrUsername = '', + blockId = '', }: DateExtensionListProps) => { const intl = useIntl(); - const { courseId } = useParams(); + const { courseId = '' } = useParams<{ courseId: string }>(); const [page, setPage] = useState(0); const { data = { count: 0, results: [], numPages: 0 }, isLoading } = useDateExtensions(courseId ?? '', { page, - pageSize: DATE_EXTENSIONS_PAGE_SIZE + pageSize: DATE_EXTENSIONS_PAGE_SIZE, + emailOrUsername, + blockId }); const tableColumns = [ diff --git a/src/dateExtensions/components/SelectGradedSubsection.tsx b/src/dateExtensions/components/SelectGradedSubsection.tsx index f1c7b2e8..a4d000e5 100644 --- a/src/dateExtensions/components/SelectGradedSubsection.tsx +++ b/src/dateExtensions/components/SelectGradedSubsection.tsx @@ -5,13 +5,15 @@ import { useGradedSubsections } from '../data/apiHook'; interface SelectGradedSubsectionProps { label?: string, placeholder: string, + value?: string, onChange: (event: React.ChangeEvent) => void, } -const SelectGradedSubsection = ({ label, placeholder, onChange }: SelectGradedSubsectionProps) => { +const SelectGradedSubsection = ({ label, placeholder, value, onChange }: SelectGradedSubsectionProps) => { const { courseId = '' } = useParams<{ courseId: string }>(); const { data = { items: [] } } = useGradedSubsections(courseId); const selectOptions = [{ displayName: placeholder, subsectionId: '' }, ...data.items]; + const handleChange = (event: React.ChangeEvent) => { onChange(event); }; @@ -19,7 +21,14 @@ const SelectGradedSubsection = ({ label, placeholder, onChange }: SelectGradedSu return ( {label && {label}} - + { selectOptions.map((option) => (