Skip to content

Commit 496d25b

Browse files
authored
Merge pull request #204 from AvaCodeSolutions/components-lazy-loading
Lazy loading components
2 parents 778cdb8 + 7f26408 commit 496d25b

8 files changed

Lines changed: 635 additions & 178 deletions

File tree

frontend/package-lock.json

Lines changed: 516 additions & 112 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
"@eslint/js": "^9.33.0",
3636
"@types/react": "^19.1.10",
3737
"@types/react-dom": "^19.1.7",
38-
"@vitejs/plugin-react": "^5.0.0",
38+
"@vitejs/plugin-react-swc": "^4.2.3",
3939
"eslint": "^9.33.0",
4040
"eslint-plugin-react-hooks": "^7.0.0",
4141
"eslint-plugin-react-refresh": "^0.5.0",
4242
"globals": "^17.0.0",
43+
"lightningcss": "^1.31.1",
4344
"sass-embedded": "^1.96.0",
4445
"vite": "^7.1.2"
4546
}

frontend/platform/course/Course.jsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ import FilterListIcon from '@mui/icons-material/FilterList';
77
import DescriptionIcon from '@mui/icons-material/Description';
88
import BallotIcon from '@mui/icons-material/Ballot';
99
import { useState, useEffect } from 'react';
10-
import { Box, Grid, Button, Dialog, Typography } from '@mui/material'
10+
import { Box, Grid, Button, Dialog, LinearProgress, Typography } from '@mui/material'
1111
import { useTheme } from '@mui/material/styles';
12-
import LessonForm from './components/LessonForm.jsx';
13-
import QuizForm from './components/QuizForm.jsx';
1412
import ContentTable from './components/ContentTable.jsx';
15-
import DeleteContentForm from './components/DeleteContentForm.jsx';
1613
import { PieChart } from '@mui/x-charts/PieChart'
1714
import { BarChart } from '@mui/x-charts/BarChart';
1815
import { getCookie } from '../../src/utils.js';
16+
import { lazy, Suspense } from "react";
17+
18+
const QuizForm = lazy(() => import("./components/QuizForm.jsx"));
19+
const LessonForm = lazy(() => import("./components/LessonForm.jsx"));
20+
const DeleteContentForm = lazy(() => import("./components/DeleteContentForm.jsx"));
1921

2022

2123
function Course() {
@@ -140,7 +142,7 @@ function Course() {
140142
if (content.type == 'lesson') {
141143
console.log("Opening lesson editor for content:", content);
142144
setDialogOpen(true);
143-
setDialogContent(<LessonForm
145+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><LessonForm
144146
header={localeMessages["update_lesson"]}
145147
initialTitle={content.lesson.title}
146148
initialContent={content.lesson.content}
@@ -150,11 +152,11 @@ function Course() {
150152
courseId={course_id}
151153
lessonId={content.lesson.id}
152154
initialWaitingPeriod={content.waiting_period}
153-
contentId={content.id} />);
155+
contentId={content.id} /></Suspense>);
154156
} else if (content.type == 'quiz') {
155157
console.log("Opening quiz editor for content:", content);
156158
setDialogOpen(true);
157-
setDialogContent(<QuizForm
159+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><QuizForm
158160
cancelCallback={() => setDialogOpen(false)}
159161
successCallback={resetDialog}
160162
courseId={course_id}
@@ -166,7 +168,7 @@ function Course() {
166168
initialWaitingPeriod={content.waiting_period}
167169
initialStrategy={content.quiz.selection_strategy}
168170
initialDeadlineDays={content.quiz.deadline_days}
169-
/>);
171+
/></Suspense>);
170172
}
171173
}
172174
if (event.type === 'content_reordered') {
@@ -190,7 +192,7 @@ function Course() {
190192
.catch(error => console.error('Error reordering contents:', error));
191193
}
192194
if (event.type === 'delete_content') {
193-
setDialogContent(<DeleteContentForm content={event.content} onDelete={deletContent} onCancel={() => {setDialogOpen(false); setDialogMaxWidth('lg');}} />);
195+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><DeleteContentForm content={event.content} onDelete={deletContent} onCancel={() => {setDialogOpen(false); setDialogMaxWidth('lg');}} /></Suspense>);
194196
setDialogMaxWidth('sm');
195197
setDialogOpen(true);
196198
}
@@ -211,19 +213,19 @@ function Course() {
211213
<Grid size={{xs: 12, md: 9}} py={2} pl={2}>
212214
<Box p={2} sx={{ border: '1px solid', borderColor: 'grey.300', borderRadius: 1, minHeight: 300 }}>
213215
{userRole !== 'viewer' && <><Button variant="contained" startIcon={<DescriptionIcon sx={{ marginLeft: direction == 'rtl' ? 1 : 0 }} />} sx={{ marginBottom: 2 }} onClick={() => {
214-
setDialogContent(<LessonForm
216+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><LessonForm
215217
header={localeMessages["new_lesson"]}
216218
initialContent={lessonCache}
217219
onContentChange={setLessonCache}
218220
cancelCallback={() => setDialogOpen(false)}
219221
successCallback={resetDialog}
220-
courseId={course_id} />);
222+
courseId={course_id} /></Suspense>);
221223
setDialogOpen(true);}}>{localeMessages["add_lesson"]}</Button>
222224
<Button variant="contained" startIcon={<BallotIcon sx={{ marginLeft: direction == 'rtl' ? 1 : 0 }} />} sx={{ marginBottom: 2, marginLeft: 1, marginRight: 1 }} onClick={() => {
223-
setDialogContent(<QuizForm
225+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><QuizForm
224226
cancelCallback={() => setDialogOpen(false)}
225227
successCallback={resetDialog}
226-
courseId={course_id} />);
228+
courseId={course_id} /></Suspense>);
227229
setDialogOpen(true);}}>{localeMessages["add_quiz"]}</Button></> }
228230
<ContentTable courseId={course_id} loaded={contentLoaded} eventHandler={(event) => tableEventHandler(event)} />
229231
</Box>

frontend/platform/courses/Courses.jsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
import 'vite/modulepreload-polyfill'
22
import { useState, useEffect } from 'react'
3-
import { Grid, Box, Link, Button, IconButton, Dialog, Paper, Switch, TableContainer, Table, TableHead, TableRow,TableBody, TableCell } from '@mui/material'
3+
import Grid from '@mui/material/Grid';
4+
import Box from '@mui/material/Box';
5+
import Link from '@mui/material/Link';
6+
import Button from '@mui/material/Button';
7+
import IconButton from '@mui/material/IconButton';
8+
import Dialog from '@mui/material/Dialog';
9+
import LinearProgress from '@mui/material/LinearProgress';
10+
import Paper from '@mui/material/Paper';
11+
import Switch from '@mui/material/Switch';
12+
import TableContainer from '@mui/material/TableContainer';
13+
import Table from '@mui/material/Table';
14+
import TableHead from '@mui/material/TableHead';
15+
import TableRow from '@mui/material/TableRow';
16+
import TableBody from '@mui/material/TableBody';
17+
import TableCell from '@mui/material/TableCell';
418
import Base from '../../src/components/Base.jsx'
5-
import CourseForm from './components/CourseForm.jsx';
619
import FilterListIcon from '@mui/icons-material/FilterList';
720
import SchoolIcon from '@mui/icons-material/School';
821
import DeleteIcon from '@mui/icons-material/Delete';
922
import EditIcon from '@mui/icons-material/Edit';
1023
import render from '../../src/render.jsx';
1124
import { getCookie } from '../../src/utils.js';
12-
import EnableCourseSwitchPopup from './components/EnableCourseSwitchPopup.jsx';
13-
import DeleteCoursePopup from './components/DeleteCoursePopup.jsx';
1425
import FilterForm from './components/FilterForm.jsx';
26+
import { lazy, Suspense } from "react";
27+
28+
const CourseForm = lazy(() => import("./components/CourseForm.jsx"));
29+
const EnableCourseSwitchPopup = lazy(() => import("./components/EnableCourseSwitchPopup.jsx"));
30+
const DeleteCoursePopup = lazy(() => import("./components/DeleteCoursePopup.jsx"));
31+
1532

1633

1734
function Courses() {
@@ -62,7 +79,7 @@ function Courses() {
6279
console.log(`${action} course with ID:`, courseId);
6380
console.log('Event:', event.target.checked);
6481
setDialogContent(<Grid sx={{ p: 2 }}>
65-
<EnableCourseSwitchPopup courseId={courseId} action={action} courseTitle={courseTitle} handleClose={() => setDialogOpen(false)} handleSuccess={updateCourseState}/>
82+
<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><EnableCourseSwitchPopup courseId={courseId} action={action} courseTitle={courseTitle} handleClose={() => setDialogOpen(false)} handleSuccess={updateCourseState}/></Suspense>
6683
</Grid>);
6784
setDialogOpen(true);
6885
}
@@ -78,7 +95,7 @@ function Courses() {
7895
};
7996

8097
const showEditCourseDialog = (course) => {
81-
setDialogContent(<CourseForm
98+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><CourseForm
8299
successCallback={(data) => {
83100
const index = courses.findIndex(item => item.id === course.id);
84101
courses[index] = data;
@@ -92,7 +109,7 @@ function Courses() {
92109
activeOrganizationId={organizationId}
93110
createMode={false}
94111
courseId={course.id}
95-
/>);
112+
/></Suspense>);
96113
setDialogOpen(true);
97114
}
98115

@@ -108,13 +125,13 @@ function Courses() {
108125
<Grid size={{xs: 12, md: 9}} py={2} pl={2}>
109126
<Box p={2} sx={{ border: '1px solid', borderColor: 'border.main', backgroundColor: 'background.main', borderRadius: 1, minHeight: 300 }}>
110127
{userRole !== 'viewer' && <Button variant="contained" startIcon={<SchoolIcon sx={{ marginLeft: direction === 'rtl' ? 1 : 0 }} />} sx={{ marginBottom: 2 }} onClick={() => {
111-
setDialogContent(<CourseForm
128+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><CourseForm
112129
successCallback={handleCourseCreated}
113130
failureCallback={handleCourseCreationFailed}
114131
cancelCallback={() => setDialogOpen(false)}
115132
activeOrganizationId={organizationId}
116133
createMode={true}
117-
/>);
134+
/></Suspense>);
118135
setDialogOpen(true);}}>{localeMessages["add_course"]}</Button>}
119136
<TableContainer component={Paper}>
120137
<Table sx={{ width: "100%" }} aria-label={localeMessages["courses"]}>
@@ -148,10 +165,10 @@ function Courses() {
148165
<IconButton onClick={() => {
149166
showEditCourseDialog(course);}}><EditIcon fontSize="small" /></IconButton>
150167
<IconButton aria-label={`Delete ${course.title}`} onClick={() => {
151-
setDialogContent(<DeleteCoursePopup courseId={course.id} courseTitle={course.title} handleClose={() => setDialogOpen(false)} handleSuccess={() => {
168+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><DeleteCoursePopup courseId={course.id} courseTitle={course.title} handleClose={() => setDialogOpen(false)} handleSuccess={() => {
152169
const index = courses.findIndex(item => item.id === course.id);
153170
setCourses(courses.filter((_, i) => i !== index));
154-
}} />);
171+
}} /></Suspense>);
155172
setDialogOpen(true);
156173
}}><DeleteIcon fontSize="small" /></IconButton>
157174
</TableCell>}
@@ -163,7 +180,7 @@ function Courses() {
163180
</Box>
164181
</Grid>
165182
<Grid display={{xs: "none", md: "block"}} size={{ md: 3 }} p={2}>
166-
<Box p={2} sx={{ border: '1px solid', borderColor: 'border.main', borderRadius: 1, minHeight: 300, position: 'sticky', top: 80, backgroundColor: 'background.main' }}>
183+
<Box p={2} sx={{ border: '1px solid', borderColor: 'border.main', borderRadius: 1, minHeight: 300, position: 'sticky', top: 85, backgroundColor: 'background.main' }}>
167184
<FilterForm onStatusChange={(params) => setQueryParameters(params)} />
168185
</Box>
169186
</Grid>

frontend/platform/learners/Learners.jsx

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,13 @@ import SchoolIcon from '@mui/icons-material/School';
1313
import BackspaceIcon from '@mui/icons-material/Backspace';
1414
import render from '../../src/render.jsx';
1515
import { getCookie } from '../../src/utils.js';
16+
import { lazy, Suspense } from "react";
1617

17-
18-
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
18+
const EnrollentList = lazy(() => import("./components/EnrollmentList.jsx"));
1919

2020

21-
function EnrolmentList({enrollments, selectHandler}) {
22-
if (enrollments.length === 0) {
23-
return <Typography component="span">{localeMessages["nor_enrollments_found"]}</Typography>
24-
}
21+
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
2522

26-
return (
27-
<TableContainer component={Paper} sx={{ maxHeight: 300, border: '1px solid', borderColor: 'grey.300', borderRadius: 1 }}>
28-
<Table>
29-
<TableHead>
30-
<TableRow>
31-
<TableCell align={direction=="rtl" ? "right" : "left"}>{localeMessages["course"]}</TableCell>
32-
<TableCell align={direction=="rtl" ? "right" : "left"}>{localeMessages["status"]}</TableCell>
33-
</TableRow>
34-
</TableHead>
35-
<TableBody>
36-
{enrollments.map((enrollment) => (
37-
<TableRow
38-
key={enrollment.id} sx={(theme) => ({':hover': {backgroundColor: theme.palette.background.dark, cursor: 'pointer', borderBottomColor: 'secondary.light', borderBottomWidth: 2, borderBottomStyle: 'solid'}})}
39-
onClick={() => selectHandler(enrollment.id)}>
40-
<TableCell align={direction=="rtl" ? "right" : "left"}>{enrollment.course_title}</TableCell>
41-
<TableCell align={direction=="rtl" ? "right" : "left"}>{localeMessages[enrollment.status]}</TableCell>
42-
</TableRow>
43-
))}
44-
</TableBody>
45-
</Table>
46-
</TableContainer>
47-
)
48-
}
4923

5024
function Learners(initialQs="") {
5125

@@ -242,7 +216,7 @@ function Learners(initialQs="") {
242216
<Typography component="span">{learner.email}</Typography>
243217
</AccordionSummary>
244218
<AccordionDetails>
245-
<Typography component="span">{learner.state === 0 ? "" : learner.state === 1 ? <LinearProgress /> : <EnrolmentList enrollments={learner.enrollments} selectHandler={showEnrollmentStatus}/>}</Typography>
219+
<Typography component="span">{learner.state === 0 ? "" : learner.state === 1 ? <LinearProgress /> : <Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><EnrollentList enrollments={learner.enrollments} selectHandler={showEnrollmentStatus}/></Suspense>}</Typography>
246220
</AccordionDetails>
247221
</Accordion>
248222
</TableCell>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography } from '@mui/material';
2+
3+
function EnrollentList({enrollments, selectHandler}) {
4+
if (enrollments.length === 0) {
5+
return <Typography component="span">{localeMessages["nor_enrollments_found"]}</Typography>
6+
}
7+
8+
return (
9+
<TableContainer component={Paper} sx={{ maxHeight: 300, border: '1px solid', borderColor: 'grey.300', borderRadius: 1 }}>
10+
<Table>
11+
<TableHead>
12+
<TableRow>
13+
<TableCell align={direction=="rtl" ? "right" : "left"}>{localeMessages["course"]}</TableCell>
14+
<TableCell align={direction=="rtl" ? "right" : "left"}>{localeMessages["status"]}</TableCell>
15+
</TableRow>
16+
</TableHead>
17+
<TableBody>
18+
{enrollments.map((enrollment) => (
19+
<TableRow
20+
key={enrollment.id} sx={(theme) => ({':hover': {backgroundColor: theme.palette.background.dark, cursor: 'pointer', borderBottomColor: 'secondary.light', borderBottomWidth: 2, borderBottomStyle: 'solid'}})}
21+
onClick={() => selectHandler(enrollment.id)}>
22+
<TableCell align={direction=="rtl" ? "right" : "left"}>{enrollment.course_title}</TableCell>
23+
<TableCell align={direction=="rtl" ? "right" : "left"}>{localeMessages[enrollment.status]}</TableCell>
24+
</TableRow>
25+
))}
26+
</TableBody>
27+
</Table>
28+
</TableContainer>
29+
)
30+
}
31+
32+
export default EnrollentList;

frontend/platform/organizations/Organizations.jsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import Base from "../../src/components/Base";
2-
import { Alert, Box, Button, Dialog, Grid, IconButton, Paper, TableContainer, Table, TableHead, TableRow,TableBody, TableCell, Typography } from "@mui/material";
2+
import Alert from "@mui/material/Alert"
3+
import Box from "@mui/material/Box"
4+
import Button from "@mui/material/Button"
5+
import Dialog from "@mui/material/Dialog"
6+
import Grid from "@mui/material/Grid"
7+
import IconButton from "@mui/material/IconButton"
8+
import LinearProgress from "@mui/material/LinearProgress"
9+
import Paper from "@mui/material/Paper"
10+
import TableContainer from "@mui/material/TableContainer"
11+
import Table from "@mui/material/Table"
12+
import TableHead from "@mui/material/TableHead"
13+
import TableRow from "@mui/material/TableRow"
14+
import TableBody from "@mui/material/TableBody"
15+
import TableCell from "@mui/material/TableCell"
16+
import Typography from "@mui/material/Typography"
317
import AddIcon from '@mui/icons-material/Add';
418
import PublicIcon from '@mui/icons-material/Public';
519
import DeleteIcon from '@mui/icons-material/Delete';
620
import EditIcon from '@mui/icons-material/Edit';
721
import { useState, useEffect, use } from "react";
822
import { getCookie } from "../../src/utils";
923
import render from "../../src/render";
10-
import OrganizationForm from "./components/OrganizationForm";
24+
import { lazy, Suspense } from "react";
25+
26+
const OrganizationForm = lazy(() => import("./components/OrganizationForm.jsx"));
1127

1228
function Organizations() {
1329
const [dialogOpen, setDialogOpen] = useState(false);
@@ -93,12 +109,12 @@ function Organizations() {
93109
<Grid size={12} py={2} pl={2}>
94110
<Box p={2} sx={{ border: '1px solid', borderColor: 'grey.300', borderRadius: 1, minHeight: 300, width: { lg: '80%' } }}>
95111
<Button variant="contained" startIcon={<AddIcon sx={{ marginLeft: direction == 'rtl' ? 1 : 0 }} />} sx={{ marginBottom: 2 }} onClick={() => {
96-
setDialogContent(<OrganizationForm
112+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><OrganizationForm
97113
successCallback={handleSuccessFormSubmission}
98114
failureCallback={handleFailedFormSubmission}
99115
cancelCallback={() => setDialogOpen(false)}
100116
createMode={true}
101-
/>);
117+
/></Suspense>);
102118
setDialogOpen(true);
103119
}}>{localeMessages["add_organization"]}</Button>
104120

@@ -117,7 +133,7 @@ function Organizations() {
117133
<TableCell>
118134
<IconButton onClick={() => goToUrl(org.public_url)}><PublicIcon fontSize="small"/></IconButton>
119135
<IconButton onClick={() => {
120-
setDialogContent(<OrganizationForm
136+
setDialogContent(<Suspense fallback={<Box sx={{ p: 2 }}><LinearProgress /></Box>}><OrganizationForm
121137
successCallback={handleSuccessFormSubmission}
122138
failureCallback={handleFailedFormSubmission}
123139
cancelCallback={() => setDialogOpen(false)}
@@ -126,7 +142,7 @@ function Organizations() {
126142
initialDescription={org.description}
127143
initialLogoUrl={org.logo}
128144
organizationId={org.id}
129-
/>);
145+
/></Suspense>);
130146
setDialogOpen(true);
131147
}}><EditIcon fontSize="small"/></IconButton>
132148
<IconButton onClick={() => deleteConfirmationDialog(org)}><DeleteIcon fontSize="small" /></IconButton>

0 commit comments

Comments
 (0)