Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions apps/src/templates/studentSnapshot/lessonInsightWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ const LessonInsightWidget: React.FC<LessonInsightWidgetProps> = ({
setInsightData(parsedData);
} catch (parseError) {
console.error('Error parsing insight JSON:', parseError);
setError('Failed to parse insight data');
setError('Error loading insight data');
}
}
})
.catch(error => {
// Only update state if this request is still current
if (currentRequestRef.current === requestId) {
console.error('Error fetching insight prompt:', error);
setError('Failed to fetch insight data');
setError('Error loading insight data');
}
})
.finally(() => {
Expand Down Expand Up @@ -107,8 +107,19 @@ const LessonInsightWidget: React.FC<LessonInsightWidgetProps> = ({
},
]}
>
{error && <Typography color="error">{error}</Typography>}
{insightData && (
{error && (
<div className={styles.widgetBody}>
<div className={styles.errorContainer}>
<FontAwesomeV6Icon
iconName="triangle-exclamation"
iconStyle="regular"
aria-label="Error"
/>
<Typography color="error">{error}</Typography>
</div>
</div>
)}
{insightData && !error && (
<div className={styles.widgetBody}>
<div>
<div className={styles.insightText}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
justify-content: center;
overflow: hidden;

div.errorContainer {
text-align: center;
justify-content: center;

i {
color: var(--text-error-secondary);
font-size: 40px;
margin-bottom: 4px;
}
}

div {
position: relative;
background: var(--background-neutral-primary, #fff);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {Typography} from '@mui/material';
import classNames from 'classnames';
import React from 'react';

import Spinner from '@cdo/apps/sharedComponents/Spinner';

import styles from './studentLessonProgressDetailsWidget.module.scss';

interface ProgressDetailProps {
Expand All @@ -13,6 +15,7 @@ interface ProgressDetailProps {
displayStudentDetailAsComplete: boolean;
showAvgComparisonArrow: boolean;
studentIsAboveClassAvg: boolean;
isLoading: boolean;
}

const ProgressDetail: React.FC<ProgressDetailProps> = ({
Expand All @@ -23,32 +26,44 @@ const ProgressDetail: React.FC<ProgressDetailProps> = ({
displayStudentDetailAsComplete,
showAvgComparisonArrow,
studentIsAboveClassAvg,
isLoading,
}) => (
<div className={styles.lessonDetail}>
<FontAwesomeV6Icon iconName={detailIconName} iconStyle={'regular'} />
<div
className={classNames(
styles.lessonDetailLabelAndInfo,
displayStudentDetailAsComplete && styles.greenCompletedText
)}
>
<Typography variant="overline3">{detailTitle}</Typography>
<Typography variant="h4">{selectedStudentDetail}</Typography>
<div
className={classNames(
styles.classAvgInfo,
studentIsAboveClassAvg ? styles.aboveClassAvg : styles.belowClassAvg
)}
>
<Typography variant="body4">{classAvgDetail}</Typography>
{showAvgComparisonArrow && (
<FontAwesomeV6Icon
iconName={studentIsAboveClassAvg ? 'arrow-up' : 'arrow-down'}
iconStyle={'regular'}
/>
)}
<div className={classNames(styles.lessonDetail, isLoading && styles.loading)}>
{isLoading && (
<div className={styles.loadingSpinner}>
<Spinner />
</div>
</div>
)}
{!isLoading && (
<>
<FontAwesomeV6Icon iconName={detailIconName} iconStyle={'regular'} />
<div
className={classNames(
styles.lessonDetailLabelAndInfo,
displayStudentDetailAsComplete && styles.greenCompletedText
)}
>
<Typography variant="overline3">{detailTitle}</Typography>
<Typography variant="h4">{selectedStudentDetail}</Typography>
<div
className={classNames(
styles.classAvgInfo,
studentIsAboveClassAvg
? styles.aboveClassAvg
: styles.belowClassAvg
)}
>
<Typography variant="body4">{classAvgDetail}</Typography>
{showAvgComparisonArrow && (
<FontAwesomeV6Icon
iconName={studentIsAboveClassAvg ? 'arrow-up' : 'arrow-down'}
iconStyle={'regular'}
/>
)}
</div>
</div>
</>
)}
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,12 @@ const formatTimeSpent = (secondsSpent: number) => {
const StudentLessonProgressDetailsWidget: React.FC<
StudentLessonProgressDetailsWidgetProps
> = ({selectedUnitId, selectedLessonId, selectedStudentId}) => {
const unitDataByUnit = useAppSelector(
state => state.sectionProgress?.unitDataByUnit
);
const lessonProgressByUnit = useAppSelector(
state => state.sectionProgress?.studentLessonProgressByUnit
);
const studentLevelProgressByUnit = useAppSelector(
state => state.sectionProgress?.studentLevelProgressByUnit
);
const {
unitDataByUnit,
studentLessonProgressByUnit,
studentLevelProgressByUnit,
isLoadingProgress,
} = useAppSelector(state => state.sectionProgress);
const studentIds = useAppSelector(state =>
state.teacherSections?.selectedStudents?.map(student => student.id)
);
Expand All @@ -85,11 +82,12 @@ const StudentLessonProgressDetailsWidget: React.FC<
const progressByUser: UserProgressByLessonData = {};
const progressAverages: ProgressAveragesByLessonData = {};
if (
!isLoadingProgress &&
unitDataByUnit &&
studentIds &&
studentIds.length > 0 &&
lessonProgressByUnit &&
lessonProgressByUnit[selectedUnitId]
studentLessonProgressByUnit &&
studentLessonProgressByUnit[selectedUnitId]
) {
const lessons = unitDataByUnit[selectedUnitId]?.lessons;
if (lessons) {
Expand All @@ -102,7 +100,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
// Iterate through students in this section
studentIds.forEach(userId => {
const unitProgressForUser =
lessonProgressByUnit[selectedUnitId][userId];
studentLessonProgressByUnit[selectedUnitId][userId];
const lessonProgressForUser = unitProgressForUser
? unitProgressForUser[lesson.id]
: null;
Expand Down Expand Up @@ -141,12 +139,18 @@ const StudentLessonProgressDetailsWidget: React.FC<
userProgressByLesson: progressByUser,
progressAveragesByLesson: progressAverages,
};
}, [unitDataByUnit, lessonProgressByUnit, studentIds, selectedUnitId]);
}, [
unitDataByUnit,
studentLessonProgressByUnit,
studentIds,
selectedUnitId,
isLoadingProgress,
]);

// Map each lesson to the number of validated levels it has
const lessonsToValidationLevels = React.useMemo(() => {
const lessonsToValidationLevelsMap: {[lessonId: number]: string[]} = {};
if (unitDataByUnit) {
if (!isLoadingProgress && unitDataByUnit) {
const lessons = unitDataByUnit[selectedUnitId]?.lessons;
if (lessons) {
Object.values(lessons).forEach(lesson => {
Expand All @@ -161,7 +165,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
}
}
return lessonsToValidationLevelsMap;
}, [unitDataByUnit, selectedUnitId]);
}, [unitDataByUnit, selectedUnitId, isLoadingProgress]);

// Map each lesson to both the amount of validation levels each student has completed and
// the class average of it
Expand All @@ -173,6 +177,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
{};

if (
!isLoadingProgress &&
lessonsToValidationLevels &&
studentLevelProgressByUnit &&
studentLevelProgressByUnit[selectedUnitId]
Expand Down Expand Up @@ -211,6 +216,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
lessonsToValidationLevels,
studentIds,
studentLevelProgressByUnit,
isLoadingProgress,
]);

const selectedStudentLessonProgressInfo = React.useMemo(() => {
Expand Down Expand Up @@ -274,6 +280,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
studentIsAboveClassAvg={
selectedStudentLessonProgress > classAvgLessonProgress
}
isLoading={isLoadingProgress}
/>
<ProgressDetail
detailTitle={'Validation tests'}
Expand All @@ -293,6 +300,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
numValidationLevelsUserCompleted >
classAvgNumValidationLevelsCompleted
}
isLoading={isLoadingProgress}
/>
<ProgressDetail
detailTitle={'Time spent'}
Expand All @@ -306,6 +314,7 @@ const StudentLessonProgressDetailsWidget: React.FC<
studentIsAboveClassAvg={
selectedStudentLessonTimeSpent < classAvgLessonTimeSpent
}
isLoading={isLoadingProgress}
/>
</div>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
flex-direction: row;
gap: 0.25rem;

.loading {
justify-content: center;

.loadingSpinner {
i {
padding: 0;
}
}
}

.lessonDetail {
display: flex;
flex-direction: row;
Expand Down
1 change: 1 addition & 0 deletions apps/src/types/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ interface ManageStudentsState {
}

interface SectionProgressState {
isLoadingProgress: boolean;
unitDataByUnit?: {
[unitId: number]: {
id: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ describe('LessonInsightWidget', () => {
renderComponent();

await waitFor(() => {
screen.getByText('Failed to fetch insight data');
screen.getByText('Error loading insight data');
screen.getByLabelText('Error');
});
});

Expand All @@ -131,7 +132,8 @@ describe('LessonInsightWidget', () => {
renderComponent();

await waitFor(() => {
screen.getByText('Failed to parse insight data');
screen.getByText('Error loading insight data');
screen.getByLabelText('Error');
});
});

Expand Down