Skip to content

Commit c3caddf

Browse files
chore: abstract graded select
1 parent 7ba8c23 commit c3caddf

File tree

4 files changed

+119
-48
lines changed

4 files changed

+119
-48
lines changed

src/data/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,10 @@ export const addDateExtension = async (courseId, extensionData: AddDateExtension
4646
const { data } = await getAuthenticatedHttpClient().post(`${getApiBaseUrl()}/api/instructor/v2/courses/${courseId}/change_due_date`, extensionData);
4747
return camelCaseObject(data);
4848
};
49+
50+
export const getGradedSubsections = async (courseId: string) => {
51+
const { data } = await getAuthenticatedHttpClient().get(
52+
`${getApiBaseUrl()}/api/instructor/v2/courses/${courseId}/graded_subsections/`
53+
);
54+
return camelCaseObject(data);
55+
};

src/data/apiHook.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2-
import { getCourseInfo, getDateExtensions, resetDateExtension, PaginationQueryKeys, addDateExtension } from './api';
2+
import { getCourseInfo, getDateExtensions, resetDateExtension, PaginationQueryKeys, addDateExtension, getGradedSubsections } from './api';
33
import { appId } from '../constants';
44

55
const COURSE_INFO_QUERY_KEY = ['courseInfo'];
@@ -10,6 +10,11 @@ const dateExtensionsQueryKeys = {
1010
byCoursePaginated: (courseId: string, pagination: PaginationQueryKeys) => [...dateExtensionsQueryKeys.byCourse(courseId), pagination.page] as const,
1111
};
1212

13+
const gradedSubsectionsQueryKeys = {
14+
all: [appId, 'gradedSubsections'] as const,
15+
byCourse: (courseId: string) => [...gradedSubsectionsQueryKeys.all, courseId] as const,
16+
};
17+
1318
export const useCourseInfo = (courseId: string) => (
1419
useQuery({
1520
queryKey: COURSE_INFO_QUERY_KEY,
@@ -45,3 +50,10 @@ export const useAddDateExtensionMutation = () => {
4550
},
4651
});
4752
};
53+
54+
export const useGradedSubsections = (courseId: string) => (
55+
useQuery({
56+
queryKey: gradedSubsectionsQueryKeys.byCourse(courseId),
57+
queryFn: () => getGradedSubsections(courseId),
58+
})
59+
);

src/dateExtensions/components/AddExtensionModal.tsx

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { ActionRow, Button, FormAutosuggest, FormAutosuggestOption, FormControl, FormGroup, FormLabel, ModalDialog } from '@openedx/paragon';
1+
import { useState } from 'react';
2+
import { ActionRow, Button, Form, FormControl, FormGroup, FormLabel, ModalDialog } from '@openedx/paragon';
23
import { useIntl } from '@openedx/frontend-base';
34
import SpecifyLearnerField from '../../components/SpecifyLearnerField/SpecifyLearnerField';
45
import messages from '../messages';
6+
import SelectGradedSubsection from './SelectGradedSubsection';
57

68
interface AddExtensionModalProps {
79
isOpen: boolean,
@@ -17,74 +19,83 @@ interface AddExtensionModalProps {
1719

1820
const AddExtensionModal = ({ isOpen, title, onClose, onSubmit }: AddExtensionModalProps) => {
1921
const intl = useIntl();
22+
const [formData, setFormData] = useState({
23+
email_or_username: '',
24+
block_id: '',
25+
due_date: '',
26+
due_time: '',
27+
reason: '',
28+
});
2029

21-
const options = [
22-
{ label: 'is an example', value: 'example' },
23-
{ label: 'another example', value: 'another' }
24-
];
25-
26-
const handleSubmit = () => {
30+
const handleSubmit = (event) => {
31+
event.preventDefault();
32+
const { email_or_username, block_id, due_date, due_time, reason } = formData;
2733
onSubmit({
28-
email_or_username: 'dianasalas',
29-
block_id: 'block-v1:DV-edtech+check+2025-05+type@sequential+block@a9500056bbb544ea82fad0d3957c6932',
30-
due_datetime: '2025-01-21 00:00:00',
31-
reason: 'Personal reasons'
34+
email_or_username,
35+
block_id,
36+
due_datetime: `${due_date} ${due_time}`,
37+
reason
3238
});
3339
};
3440

41+
const onChange = (event) => {
42+
const { name, value } = event.target;
43+
setFormData((prevData) => ({
44+
...prevData,
45+
[name]: value,
46+
}));
47+
};
48+
3549
return (
3650
<ModalDialog isOpen={isOpen} onClose={onClose} title={title} isOverflowVisible={false} size="xl">
37-
<ModalDialog.Header className="p-3 pl-4">
38-
<h3>{title}</h3>
39-
</ModalDialog.Header>
40-
<ModalDialog.Body className="border-bottom border-top">
41-
<div className="pt-3">
42-
<p>{intl.formatMessage(messages.extensionInstructions)}</p>
43-
<FormGroup size="sm">
51+
<Form onSubmit={handleSubmit}>
52+
<ModalDialog.Header className="p-3 pl-4">
53+
<h3>{title}</h3>
54+
</ModalDialog.Header>
55+
<ModalDialog.Body className="border-bottom border-top">
56+
<div className="pt-3">
57+
<p>{intl.formatMessage(messages.extensionInstructions)}</p>
4458
<div className="container-fluid border-bottom mb-4.5 pb-3">
4559
<div className="row">
4660
<div className="col-sm-12 col-md-6">
4761
<SpecifyLearnerField onChange={() => {}} />
4862
</div>
4963
<div className="col-sm-12 col-md-4">
50-
<FormLabel>{intl.formatMessage(messages.selectGradedSubsection)}</FormLabel>
51-
<FormAutosuggest placeholder={intl.formatMessage(messages.selectGradedSubsection)} name="block_id">
52-
{
53-
options.map((option) => (
54-
<FormAutosuggestOption key={option.value} value={option.value} onChange={() => {}}>
55-
{option.label}
56-
</FormAutosuggestOption>
57-
))
58-
}
59-
</FormAutosuggest>
64+
<SelectGradedSubsection
65+
label={intl.formatMessage(messages.selectGradedSubsection)}
66+
placeholder={intl.formatMessage(messages.selectGradedSubsection)}
67+
onChange={onChange}
68+
/>
6069
</div>
6170
</div>
6271
</div>
6372
<div>
6473
<h4>{intl.formatMessage(messages.defineExtension)}</h4>
65-
<FormLabel>
66-
{intl.formatMessage(messages.extensionDate)}:
67-
</FormLabel>
68-
<div className="d-md-flex w-md-50 align-items-center">
69-
<FormControl name="due_date" type="date" size="md" />
70-
<FormControl name="due_time" type="time" size="md" className="mt-sm-3 mt-md-0" />
71-
</div>
72-
<div className="mt-3">
74+
<FormGroup size="sm">
75+
<FormLabel>
76+
{intl.formatMessage(messages.extensionDate)}:
77+
</FormLabel>
78+
<div className="d-md-flex w-md-50 align-items-center">
79+
<FormControl name="due_date" type="date" size="md" />
80+
<FormControl name="due_time" type="time" size="md" className="mt-sm-3 mt-md-0" />
81+
</div>
82+
</FormGroup>
83+
<FormGroup className="mt-3" size="sm">
7384
<FormLabel>
7485
{intl.formatMessage(messages.reasonForExtension)}:
7586
</FormLabel>
76-
<FormControl name="reason" placeholder="Reason for extension" size="md" />
77-
</div>
87+
<FormControl name="reason" placeholder={intl.formatMessage(messages.reasonForExtension)} size="md" />
88+
</FormGroup>
7889
</div>
79-
</FormGroup>
80-
</div>
81-
</ModalDialog.Body>
82-
<ModalDialog.Footer className="p-4">
83-
<ActionRow>
84-
<Button variant="tertiary" onClick={onClose}>{intl.formatMessage(messages.cancel)}</Button>
85-
<Button onClick={handleSubmit}>{intl.formatMessage(messages.addExtension)}</Button>
86-
</ActionRow>
87-
</ModalDialog.Footer>
90+
</div>
91+
</ModalDialog.Body>
92+
<ModalDialog.Footer className="p-4">
93+
<ActionRow>
94+
<Button variant="tertiary" onClick={onClose}>{intl.formatMessage(messages.cancel)}</Button>
95+
<Button type="submit">{intl.formatMessage(messages.addExtension)}</Button>
96+
</ActionRow>
97+
</ModalDialog.Footer>
98+
</Form>
8899
</ModalDialog>
89100
);
90101
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { FormLabel, FormControl, FormGroup } from '@openedx/paragon';
2+
import { useGradedSubsections } from '../../data/apiHook';
3+
import { useParams } from 'react-router';
4+
5+
interface SelectGradedSubsectionProps {
6+
label?: string,
7+
placeholder: string,
8+
onChange: (event: React.ChangeEvent<HTMLSelectElement>) => void,
9+
}
10+
11+
// Example API response used to test
12+
// const options = [
13+
// { displayName: 'is an example', subsectionId: 'example' },
14+
// { displayName: 'another example', subsectionId: 'another' }
15+
// ];
16+
17+
const SelectGradedSubsection = ({ label, placeholder, onChange }: SelectGradedSubsectionProps) => {
18+
const { courseId = '' } = useParams<{ courseId: string }>();
19+
const { data = { results: [] } } = useGradedSubsections(courseId);
20+
const selectOptions = [{ displayName: placeholder, subsectionId: '' }, ...data.results];
21+
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
22+
onChange(event);
23+
};
24+
25+
return (
26+
<FormGroup size="sm">
27+
{label && <FormLabel>{label}</FormLabel>}
28+
<FormControl placeholder={placeholder} name="block_id" as="select" onChange={handleChange} size="md">
29+
{
30+
selectOptions.map((option) => (
31+
<option key={option.subsectionId} value={option.subsectionId}>
32+
{option.displayName}
33+
</option>
34+
))
35+
}
36+
</FormControl>
37+
</FormGroup>
38+
);
39+
};
40+
41+
export default SelectGradedSubsection;

0 commit comments

Comments
 (0)