Skip to content

Commit fc4a2de

Browse files
committed
Add activity preview answer checking for Admin
1 parent 1250f9a commit fc4a2de

File tree

4 files changed

+154
-78
lines changed

4 files changed

+154
-78
lines changed

frontend/src/components/course_authoring/editorComponents/PreviewLearnerModal.tsx

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import { Replay } from "@mui/icons-material";
12
import CloseIcon from "@mui/icons-material/Close";
23
import {
34
Box,
5+
Button,
46
Dialog,
57
IconButton,
68
Stack,
79
Typography,
810
useTheme,
911
} from "@mui/material";
12+
import { useRef, useState } from "react";
1013
import {
1114
Activity,
1215
isMatchingActivity,
@@ -16,9 +19,12 @@ import {
1619
isTextInputActivity,
1720
} from "../../../types/CourseTypes";
1821
import MatchingViewer from "../../course_viewing/matching/MatchingViewer";
22+
import WrongAnswerModal from "../../course_viewing/modals/WrongAnswerModal";
1923
import MultipleChoiceViewer from "../../course_viewing/multiple-choice/MultipleChoiceViewer";
2024
import TableViewer from "../../course_viewing/table/TableViewer";
21-
import TextInputViewer from "../../course_viewing/text-input/TextInputViewer";
25+
import TextInputViewer, {
26+
ActivityViewerHandle,
27+
} from "../../course_viewing/text-input/TextInputViewer";
2228

2329
const PreviewLearnerModal = ({
2430
activity,
@@ -30,6 +36,9 @@ const PreviewLearnerModal = ({
3036
handleClose: () => void;
3137
}) => {
3238
const theme = useTheme();
39+
const activityPreviewRef = useRef<ActivityViewerHandle>(null);
40+
const [isCompleted, setIsCompleted] = useState(false);
41+
const [showWrongAnswerModal, setShowWrongAnswerModal] = useState(false);
3342
return (
3443
<Dialog
3544
open={open}
@@ -58,45 +67,86 @@ const PreviewLearnerModal = ({
5867
}}
5968
>
6069
<Typography variant="labelMedium">Preview (Learner View)</Typography>
61-
<IconButton onClick={handleClose}>
62-
<CloseIcon />
63-
</IconButton>
70+
<Stack direction="row" alignItems="center" gap="8px">
71+
<Button
72+
onClick={() => {
73+
if (isCompleted) {
74+
setIsCompleted(false);
75+
} else {
76+
activityPreviewRef.current?.checkAnswer();
77+
}
78+
}}
79+
sx={{
80+
height: "48px",
81+
width: "fit-content",
82+
paddingX: "16px",
83+
paddingY: "10px",
84+
gap: "8px",
85+
border: "1px solid",
86+
borderColor: theme.palette.Neutral[500],
87+
borderRadius: "4px",
88+
backgroundColor: theme.palette.Learner.Dark.Default,
89+
color: "white",
90+
}}
91+
>
92+
<Typography variant="labelLarge">
93+
{isCompleted ? (
94+
<>
95+
<Replay /> Reset
96+
</>
97+
) : (
98+
"Check Answer"
99+
)}
100+
</Typography>
101+
</Button>
102+
<IconButton onClick={handleClose}>
103+
<CloseIcon />
104+
</IconButton>
105+
</Stack>
64106
</Stack>
65107
<Box sx={{ padding: "20px" }}>
66108
{(isMultipleChoiceActivity(activity) ||
67109
isMultiSelectActivity(activity)) && (
68110
<MultipleChoiceViewer
69111
activity={activity}
70-
onWrongAnswer={() => {}}
71-
onCorrectAnswer={() => {}}
72-
isCompleted={false}
112+
onWrongAnswer={() => setShowWrongAnswerModal(true)}
113+
onCorrectAnswer={() => setIsCompleted(true)}
114+
isCompleted={isCompleted}
115+
ref={activityPreviewRef}
73116
/>
74117
)}
75118
{isTableActivity(activity) && (
76119
<TableViewer
77120
activity={activity}
78-
onWrongAnswer={() => {}}
79-
onCorrectAnswer={() => {}}
80-
isCompleted={false}
121+
onWrongAnswer={() => setShowWrongAnswerModal(true)}
122+
onCorrectAnswer={() => setIsCompleted(true)}
123+
isCompleted={isCompleted}
124+
ref={activityPreviewRef}
81125
/>
82126
)}
83127
{isMatchingActivity(activity) && (
84128
<MatchingViewer
85129
activity={activity}
86-
onWrongAnswer={() => {}}
87-
onCorrectAnswer={() => {}}
88-
isCompleted={false}
130+
onWrongAnswer={() => setShowWrongAnswerModal(true)}
131+
onCorrectAnswer={() => setIsCompleted(true)}
132+
isCompleted={isCompleted}
133+
ref={activityPreviewRef}
89134
/>
90135
)}
91136
{isTextInputActivity(activity) && (
92137
<TextInputViewer
93138
activity={activity}
94-
onWrongAnswer={() => {}}
95-
onCorrectAnswer={() => {}}
96-
isCompleted={false}
139+
onWrongAnswer={() => setShowWrongAnswerModal(true)}
140+
onCorrectAnswer={() => setIsCompleted(true)}
141+
isCompleted={isCompleted}
142+
ref={activityPreviewRef}
97143
/>
98144
)}
99145
</Box>
146+
<WrongAnswerModal
147+
open={showWrongAnswerModal}
148+
onClose={() => setShowWrongAnswerModal(false)}
149+
/>
100150
</Dialog>
101151
);
102152
};

frontend/src/components/course_viewing/modals/WrongAnswerModal.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
Typography,
77
useTheme,
88
} from "@mui/material";
9-
import React from "react";
9+
import React, { useMemo } from "react";
1010
import activityWrongAnswerTitles from "../../../constants/ActivityWrongAnswerTitles";
1111

1212
export type WrongAnswerModalProps = {
@@ -21,17 +21,31 @@ const WrongAnswerModal: React.FC<WrongAnswerModalProps> = ({
2121
hint,
2222
}) => {
2323
const theme = useTheme();
24+
const [lastUpdated, setLastUpdated] = React.useState(Date.now());
25+
const prevOpen = React.useRef(open);
26+
27+
React.useEffect(() => {
28+
if (!prevOpen.current && open) {
29+
setLastUpdated(Date.now());
30+
}
31+
prevOpen.current = open;
32+
}, [open]);
33+
34+
const title = useMemo(
35+
() =>
36+
hint
37+
? "Hint"
38+
: activityWrongAnswerTitles[
39+
Math.floor(Math.random() * activityWrongAnswerTitles.length)
40+
],
41+
// eslint-disable-next-line react-hooks/exhaustive-deps
42+
[hint, lastUpdated],
43+
);
2444

2545
const handleClose = () => {
2646
onClose();
2747
};
2848

29-
const title = hint
30-
? "Hint"
31-
: activityWrongAnswerTitles[
32-
Math.floor(Math.random() * activityWrongAnswerTitles.length)
33-
];
34-
3549
return (
3650
<Dialog
3751
open={open}
@@ -85,7 +99,7 @@ const WrongAnswerModal: React.FC<WrongAnswerModalProps> = ({
8599
color: "white",
86100
alignSelf: "flex-end",
87101
}}
88-
onClick={onClose}
102+
onClick={handleClose}
89103
>
90104
<Typography variant="labelLarge">Try Again</Typography>
91105
<RefreshIcon />

frontend/src/components/course_viewing/multiple-choice/MultipleChoiceViewOption.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export default function MultipleChoiceViewOption({
2727
iconElement = <Check />;
2828
} else if (isMultiSelect) {
2929
if (selected) {
30-
iconElement = <CheckBox />;
30+
iconElement = (
31+
<CheckBox sx={{ color: theme.palette.Learner.Dark.Default }} />
32+
);
3133
} else {
3234
iconElement = <CheckBoxOutlineBlank />;
3335
}

frontend/src/components/course_viewing/sidebar/UnitSidebar.tsx

Lines changed: 63 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import {
1515
} from "@mui/material";
1616
import React, { useEffect, useState } from "react";
1717
import CourseAPIClient from "../../../APIClients/CourseAPIClient";
18+
import { useCourseUnits } from "../../../contexts/CourseUnitsContext";
1819
import { useUser } from "../../../hooks/useUser";
1920
import { CourseUnit, UnitSidebarModalType } from "../../../types/CourseTypes";
2021
import { isAdministrator } from "../../../types/UserTypes";
2122
import CreateUnitModal from "../modals/CreateUnitModal";
2223
import DeleteUnitModal from "../modals/DeleteUnitModal";
2324
import EditUnitModal from "../modals/EditUnitModal";
2425
import ContextMenu from "./ContextMenu";
25-
import { useCourseUnits } from "../../../contexts/CourseUnitsContext";
2626

2727
interface UnitSideBarProps {
2828
handleClose: () => void;
@@ -47,7 +47,11 @@ export default function UnitSidebar({
4747
const [openEditUnitModal, setOpenEditUnitModal] = useState(false);
4848
const [openDeleteUnitModal, setOpenDeleteUnitModal] = useState(false);
4949

50-
const { courseUnits, refetchCourseUnits } = useCourseUnits();
50+
const {
51+
courseUnits,
52+
refetchCourseUnits,
53+
isLoading: isCourseUnitsLoading,
54+
} = useCourseUnits();
5155

5256
useEffect(() => {
5357
if (!courseUnits || !courseUnits.length) return;
@@ -206,62 +210,68 @@ export default function UnitSidebar({
206210
onClose={handleContextMenuClose}
207211
onModalOpen={handleOpenModal}
208212
/>
209-
<List sx={{ width: "100%" }}>
210-
{courseUnits.map((unit, index) => {
211-
return (
212-
<ListItem
213-
key={unit.id}
214-
disablePadding
215-
sx={{
216-
borderBottom: 1,
217-
borderColor:
218-
index !== courseUnits.length - 1
219-
? "#DBE4E7"
220-
: "transparent",
221-
}}
222-
>
223-
<ListItemButton
213+
{isCourseUnitsLoading ? (
214+
<Box display="flex" justifyContent="center">
215+
<Typography variant="bodyMedium">Loading units...</Typography>
216+
</Box>
217+
) : (
218+
<List sx={{ width: "100%" }}>
219+
{courseUnits.map((unit, index) => {
220+
return (
221+
<ListItem
224222
key={unit.id}
223+
disablePadding
225224
sx={{
226-
py: "15px",
227-
px: "32px",
228-
backgroundColor:
229-
selectedUnit?.id === unit.id
230-
? theme.palette[user.role].Light.Selected
225+
borderBottom: 1,
226+
borderColor:
227+
index !== courseUnits.length - 1
228+
? "#DBE4E7"
231229
: "transparent",
232-
"&:hover": {
233-
backgroundColor: theme.palette[user.role].Light.Hover,
234-
},
235230
}}
236-
onClick={(event) => handleListItemClick(event, index)}
237231
>
238-
<ListItemText
239-
disableTypography
240-
primary={`${unit.displayIndex}. ${unit.title}`}
241-
sx={
242-
selectedUnit?.id === unit.id
243-
? theme.typography.labelLargeProminent
244-
: theme.typography.bodyMedium
245-
}
246-
/>
247-
{isAdministrator(user) && (
248-
<IconButton
249-
edge="end"
250-
onClick={(event) => {
251-
event.stopPropagation(); // Prevent triggering the list item click
252-
handleContextMenuOpen(event, unit); // Custom function to handle button click
253-
}}
254-
sx={{ marginLeft: "16px" }}
255-
>
256-
<MoreVertIcon />
257-
</IconButton>
258-
)}
259-
</ListItemButton>
260-
</ListItem>
261-
);
262-
})}
263-
</List>
264-
{isAdministrator(user) && (
232+
<ListItemButton
233+
key={unit.id}
234+
sx={{
235+
py: "15px",
236+
px: "32px",
237+
backgroundColor:
238+
selectedUnit?.id === unit.id
239+
? theme.palette[user.role].Light.Selected
240+
: "transparent",
241+
"&:hover": {
242+
backgroundColor: theme.palette[user.role].Light.Hover,
243+
},
244+
}}
245+
onClick={(event) => handleListItemClick(event, index)}
246+
>
247+
<ListItemText
248+
disableTypography
249+
primary={`${unit.displayIndex}. ${unit.title}`}
250+
sx={
251+
selectedUnit?.id === unit.id
252+
? theme.typography.labelLargeProminent
253+
: theme.typography.bodyMedium
254+
}
255+
/>
256+
{isAdministrator(user) && (
257+
<IconButton
258+
edge="end"
259+
onClick={(event) => {
260+
event.stopPropagation(); // Prevent triggering the list item click
261+
handleContextMenuOpen(event, unit); // Custom function to handle button click
262+
}}
263+
sx={{ marginLeft: "16px" }}
264+
>
265+
<MoreVertIcon />
266+
</IconButton>
267+
)}
268+
</ListItemButton>
269+
</ListItem>
270+
);
271+
})}
272+
</List>
273+
)}
274+
{isAdministrator(user) && !isCourseUnitsLoading && (
265275
<Button
266276
variant="contained"
267277
onClick={handleOpenCreateUnitModal}

0 commit comments

Comments
 (0)