icleafreportui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +7 -0
- package/README.md +70 -0
- package/package.json +55 -0
- package/public/favicon.ico +0 -0
- package/public/images/EResourcesImg.png +0 -0
- package/public/images/courseCardImg.png +0 -0
- package/public/images/courseInfo.png +0 -0
- package/public/images/exam-options.png +0 -0
- package/public/images/icleaf-11.png +0 -0
- package/public/images/icleaf_logo.png +0 -0
- package/public/images/template.png +0 -0
- package/public/images/unnamed.png +0 -0
- package/public/images/user.jpg +0 -0
- package/public/images/young-man-studying-library-using-laptop-1.png +0 -0
- package/public/index.html +45 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/App.css +24 -0
- package/src/App.js +37 -0
- package/src/Login.js +159 -0
- package/src/Reports/CourseReport.js +209 -0
- package/src/Reports/ExamPackReport.js +554 -0
- package/src/Reports/ExamReport.js +269 -0
- package/src/Reports/Report.js +271 -0
- package/src/api/client.jsx +42 -0
- package/src/components/Header.css +301 -0
- package/src/components/Header.jsx +192 -0
- package/src/components/Loader.jsx +23 -0
- package/src/components/imagePathUrl.jsx +11 -0
- package/src/components/sidebar.css +947 -0
- package/src/components/sidebar.jsx +81 -0
- package/src/context/TenantProvider.jsx +22 -0
- package/src/fonts/210000.jpg +0 -0
- package/src/fonts/210001.jpg +0 -0
- package/src/fonts/210003.jpg +0 -0
- package/src/fonts/210004.jpg +0 -0
- package/src/fonts/210006.jpg +0 -0
- package/src/fonts/210018.jpg +0 -0
- package/src/fonts/210019.jpg +0 -0
- package/src/fonts/210020.jpg +0 -0
- package/src/fonts/210279.jpg +0 -0
- package/src/fonts/210280.jpg +0 -0
- package/src/fonts/Gilroy-Black.ttf +0 -0
- package/src/fonts/Gilroy-BlackItalic.ttf +0 -0
- package/src/fonts/Gilroy-Bold.ttf +0 -0
- package/src/fonts/Gilroy-BoldItalic.ttf +0 -0
- package/src/fonts/Gilroy-ExtraBold.ttf +0 -0
- package/src/fonts/Gilroy-ExtraBoldItalic.ttf +0 -0
- package/src/fonts/Gilroy-Heavy.ttf +0 -0
- package/src/fonts/Gilroy-HeavyItalic.ttf +0 -0
- package/src/fonts/Gilroy-Light.ttf +0 -0
- package/src/fonts/Gilroy-LightItalic.ttf +0 -0
- package/src/fonts/Gilroy-Medium.ttf +0 -0
- package/src/fonts/Gilroy-MediumItalic.ttf +0 -0
- package/src/fonts/Gilroy-Regular.ttf +0 -0
- package/src/fonts/Gilroy-RegularItalic.ttf +0 -0
- package/src/fonts/Gilroy-SemiBold.ttf +0 -0
- package/src/fonts/Gilroy-SemiBoldItalic.ttf +0 -0
- package/src/fonts/Gilroy-Thin.ttf +0 -0
- package/src/fonts/Gilroy-ThinItalic.ttf +0 -0
- package/src/fonts/Gilroy-UltraLight.ttf +0 -0
- package/src/fonts/Gilroy-UltraLightItalic.ttf +0 -0
- package/src/fonts/Help - Guide Document.pdf +0 -0
- package/src/fonts/License.txt +144 -0
- package/src/fonts/More Free Fonts on fontshmonts.com.url +2 -0
- package/src/fonts/cover.jpg +0 -0
- package/src/index.css +13 -0
- package/src/index.js +21 -0
- package/src/login.css +809 -0
- package/src/logo.svg +1 -0
- package/src/package.js +10 -0
- package/src/reportWebVitals.js +13 -0
- package/src/setupTests.js +5 -0
- package/src/styles.css +2026 -0
- package/src/theme.css +107 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { Autocomplete, FormControl, Button, Grid, Box, Typography, Paper, Snackbar, Alert, CircularProgress, TextField, Checkbox, FormControlLabel } from '@mui/material';
|
|
4
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
|
+
import Header from '../components/Header';
|
|
6
|
+
import Sidebar from '../components/sidebar';
|
|
7
|
+
import '../App.css';
|
|
8
|
+
|
|
9
|
+
function ExamReport() {
|
|
10
|
+
const [token, setToken] = useState(localStorage.getItem("token"));
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const [subjects, setSubjects] = useState([]);
|
|
13
|
+
const [examPacks, setExamPacks] = useState([]);
|
|
14
|
+
const [exams, setExams] = useState([]);
|
|
15
|
+
const [users, setUsers] = useState([]);
|
|
16
|
+
const [selectedSubject, setSelectedSubject] = useState(null);
|
|
17
|
+
const [selectedExamPack, setSelectedExamPack] = useState(null);
|
|
18
|
+
const [selectedExam, setSelectedExam] = useState(null);
|
|
19
|
+
const [selectedUser, setSelectedUser] = useState(null);
|
|
20
|
+
const [alertMessage, setAlertMessage] = useState('');
|
|
21
|
+
const [openAlert, setOpenAlert] = useState(false);
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
|
|
24
|
+
const baseURL = process.env.REACT_APP_BASE_URL;
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (token == null) {
|
|
28
|
+
navigate("/login")
|
|
29
|
+
} else {
|
|
30
|
+
fetchAllSubjects();
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const fetchAllSubjects = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const response = await axios({
|
|
37
|
+
method: "get",
|
|
38
|
+
url: `${baseURL}api/showAllSubjects`,
|
|
39
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
40
|
+
});
|
|
41
|
+
setSubjects(response.data);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Error fetching subjects:', error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const fetchExamPacks = async (subjectId) => {
|
|
48
|
+
try {
|
|
49
|
+
const response = await axios({
|
|
50
|
+
method: "get",
|
|
51
|
+
params: { subjectId },
|
|
52
|
+
url: `${baseURL}api/getAllExamPacks`,
|
|
53
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
54
|
+
});
|
|
55
|
+
setExamPacks(response.data);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error fetching exam packs:', error);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const fetchExams = async (examPackId) => {
|
|
62
|
+
try {
|
|
63
|
+
const response = await axios({
|
|
64
|
+
method: "get",
|
|
65
|
+
params: { examPackId },
|
|
66
|
+
url: `${baseURL}api/getAllExams`,
|
|
67
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
68
|
+
});
|
|
69
|
+
setExams(response.data);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error fetching exams:', error);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const fetchUsers = async (examId) => {
|
|
76
|
+
try {
|
|
77
|
+
const response = await axios({
|
|
78
|
+
method: "get",
|
|
79
|
+
params: { examId },
|
|
80
|
+
url: `${baseURL}api/getAllUsers`,
|
|
81
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
82
|
+
});
|
|
83
|
+
setUsers(response.data);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Error fetching users:', error);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleSubjectChange = (event, newValue) => {
|
|
90
|
+
setSelectedSubject(newValue);
|
|
91
|
+
setSelectedExamPack(null);
|
|
92
|
+
setSelectedExam(null);
|
|
93
|
+
setSelectedUser(null);
|
|
94
|
+
setExamPacks([]);
|
|
95
|
+
setExams([]);
|
|
96
|
+
setUsers([]);
|
|
97
|
+
if (newValue) {
|
|
98
|
+
fetchExamPacks(newValue.subjectId);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleExamPackChange = (event, newValue) => {
|
|
103
|
+
setSelectedExamPack(newValue);
|
|
104
|
+
setSelectedExam(null);
|
|
105
|
+
setSelectedUser(null);
|
|
106
|
+
setExams([]);
|
|
107
|
+
setUsers([]);
|
|
108
|
+
if (newValue) {
|
|
109
|
+
fetchExams(newValue.id);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleExamChange = (event, newValue) => {
|
|
114
|
+
setSelectedExam(newValue);
|
|
115
|
+
setSelectedUser(null);
|
|
116
|
+
setUsers([]);
|
|
117
|
+
if (newValue) {
|
|
118
|
+
fetchUsers(newValue.id);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleUserChange = (event, newValue) => {
|
|
123
|
+
setSelectedUser(newValue);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleDownload = async () => {
|
|
127
|
+
setLoading(true);
|
|
128
|
+
try {
|
|
129
|
+
const url = `${baseURL}api/generateReportForExam?subjectId=${selectedSubject.subjectId}&examPackId=${selectedExamPack.id}&examId=${selectedExam.id}`;
|
|
130
|
+
const response = await axios.get(url, {
|
|
131
|
+
responseType: 'blob',
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: `Bearer ${token}`
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
138
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
139
|
+
const link = document.createElement('a');
|
|
140
|
+
link.href = blobUrl;
|
|
141
|
+
link.setAttribute('download', 'QuestionewiseExamReport.xlsx');
|
|
142
|
+
document.body.appendChild(link);
|
|
143
|
+
link.click();
|
|
144
|
+
link.parentNode.removeChild(link);
|
|
145
|
+
URL.revokeObjectURL(blobUrl);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('Error downloading report:', error);
|
|
148
|
+
} finally {
|
|
149
|
+
setLoading(false);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div>
|
|
155
|
+
<Header />
|
|
156
|
+
<Sidebar />
|
|
157
|
+
<div style={{ padding: "100px 0px 0px 100px", marginLeft: "140px" }}>
|
|
158
|
+
<Box sx={{ maxWidth: 800, mx: 'auto', my: 4, p: 3, backgroundColor: '#f5f5f5', borderRadius: 2 }}>
|
|
159
|
+
<Paper elevation={6} sx={{ p: 5, textAlign: 'center', backgroundColor: '#fff' }}>
|
|
160
|
+
<Typography variant="h4" component="h1" gutterBottom sx={{ color: '#3f51b5', marginBottom: "20px" }}>
|
|
161
|
+
Exam Questions Report
|
|
162
|
+
</Typography>
|
|
163
|
+
<Grid container spacing={2} justifyContent="center">
|
|
164
|
+
<Grid item xs={12} sm={6}>
|
|
165
|
+
<FormControl fullWidth>
|
|
166
|
+
<Autocomplete
|
|
167
|
+
id="ddlSubject"
|
|
168
|
+
options={subjects}
|
|
169
|
+
getOptionLabel={(option) => option.subjectName}
|
|
170
|
+
value={selectedSubject}
|
|
171
|
+
onChange={handleSubjectChange}
|
|
172
|
+
renderInput={(params) => <TextField {...params} label="Subject" />}
|
|
173
|
+
/>
|
|
174
|
+
</FormControl>
|
|
175
|
+
</Grid>
|
|
176
|
+
|
|
177
|
+
<Grid item xs={12} sm={6}>
|
|
178
|
+
<FormControl fullWidth>
|
|
179
|
+
<Autocomplete
|
|
180
|
+
id="ddlExamPack"
|
|
181
|
+
options={examPacks}
|
|
182
|
+
getOptionLabel={(option) => option.name}
|
|
183
|
+
value={selectedExamPack}
|
|
184
|
+
onChange={handleExamPackChange}
|
|
185
|
+
renderInput={(params) => <TextField {...params} label="Exam Pack" />}
|
|
186
|
+
/>
|
|
187
|
+
</FormControl>
|
|
188
|
+
</Grid>
|
|
189
|
+
|
|
190
|
+
<Grid item xs={12} sm={6}>
|
|
191
|
+
<FormControl fullWidth>
|
|
192
|
+
<Autocomplete
|
|
193
|
+
id="ddlExam"
|
|
194
|
+
options={exams}
|
|
195
|
+
getOptionLabel={(option) => option.examName}
|
|
196
|
+
value={selectedExam}
|
|
197
|
+
onChange={handleExamChange}
|
|
198
|
+
renderInput={(params) => <TextField {...params} label="Exam" />}
|
|
199
|
+
/>
|
|
200
|
+
</FormControl>
|
|
201
|
+
</Grid>
|
|
202
|
+
|
|
203
|
+
{/* <Grid item xs={12} sm={6}>
|
|
204
|
+
<FormControl fullWidth>
|
|
205
|
+
<Autocomplete
|
|
206
|
+
id="ddlUser"
|
|
207
|
+
options={users}
|
|
208
|
+
getOptionLabel={(option) => option.userName}
|
|
209
|
+
value={selectedUser}
|
|
210
|
+
onChange={handleUserChange}
|
|
211
|
+
renderInput={(params) => <TextField {...params} label="User" />}
|
|
212
|
+
/>
|
|
213
|
+
</FormControl>
|
|
214
|
+
</Grid> */}
|
|
215
|
+
</Grid>
|
|
216
|
+
|
|
217
|
+
<Box sx={{ mt: 3, position: 'relative' }}>
|
|
218
|
+
{/* <FormControlLabel
|
|
219
|
+
control={
|
|
220
|
+
<Checkbox
|
|
221
|
+
checked={onlyWrongAnswers}
|
|
222
|
+
onChange={(e) => setOnlyWrongAnswers(e.target.checked)}
|
|
223
|
+
/>
|
|
224
|
+
}
|
|
225
|
+
label="Only Wrong Answers"
|
|
226
|
+
/> */}
|
|
227
|
+
<Box sx={{ mt: 2, position: 'relative' }}>
|
|
228
|
+
<Button
|
|
229
|
+
variant="contained"
|
|
230
|
+
color="primary"
|
|
231
|
+
onClick={handleDownload}
|
|
232
|
+
disabled={!selectedSubject || !selectedExamPack || !selectedExam || loading}
|
|
233
|
+
sx={{ width: 'auto', backgroundColor: '#3f51b5', '&:hover': { backgroundColor: '#303f9f' } }}
|
|
234
|
+
>
|
|
235
|
+
{loading ? 'Downloading...' : 'Download Report'}
|
|
236
|
+
</Button>
|
|
237
|
+
{loading && (
|
|
238
|
+
<CircularProgress
|
|
239
|
+
size={24}
|
|
240
|
+
sx={{
|
|
241
|
+
position: 'absolute',
|
|
242
|
+
top: '50%',
|
|
243
|
+
left: '50%',
|
|
244
|
+
marginTop: '-12px',
|
|
245
|
+
marginLeft: '-12px',
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
</Box>
|
|
250
|
+
</Box>
|
|
251
|
+
</Paper>
|
|
252
|
+
</Box>
|
|
253
|
+
|
|
254
|
+
<Snackbar
|
|
255
|
+
open={openAlert}
|
|
256
|
+
autoHideDuration={6000}
|
|
257
|
+
onClose={() => setOpenAlert(false)}
|
|
258
|
+
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
259
|
+
>
|
|
260
|
+
<Alert onClose={() => setOpenAlert(false)} severity="info" sx={{ width: '100%' }}>
|
|
261
|
+
{alertMessage}
|
|
262
|
+
</Alert>
|
|
263
|
+
</Snackbar>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default ExamReport;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Autocomplete, FormControl, Button, Grid, Box, Typography, Paper, Snackbar, Alert, CircularProgress, TextField, Checkbox, FormControlLabel } from '@mui/material';
|
|
3
|
+
import { useNavigate } from 'react-router-dom';
|
|
4
|
+
import Header from '../components/Header';
|
|
5
|
+
import Sidebar from '../components/sidebar';
|
|
6
|
+
import { useTenant } from '../context/TenantProvider'; // ADD THIS
|
|
7
|
+
import '../App.css';
|
|
8
|
+
|
|
9
|
+
function Report() {
|
|
10
|
+
const { apiClient } = useTenant(); // ADD THIS - gets API client with token
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const [subjects, setSubjects] = useState([]);
|
|
13
|
+
const [examPacks, setExamPacks] = useState([]);
|
|
14
|
+
const [exams, setExams] = useState([]);
|
|
15
|
+
const [users, setUsers] = useState([]);
|
|
16
|
+
const [selectedSubject, setSelectedSubject] = useState(null);
|
|
17
|
+
const [selectedExamPack, setSelectedExamPack] = useState(null);
|
|
18
|
+
const [selectedExam, setSelectedExam] = useState(null);
|
|
19
|
+
const [selectedUser, setSelectedUser] = useState(null);
|
|
20
|
+
const [alertMessage, setAlertMessage] = useState('');
|
|
21
|
+
const [openAlert, setOpenAlert] = useState(false);
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
const [onlyWrongAnswers, setOnlyWrongAnswers] = useState(false);
|
|
24
|
+
|
|
25
|
+
const baseURL = process.env.REACT_APP_BASE_URL;
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
// REMOVE token check - no more JWT
|
|
29
|
+
fetchAllSubjects();
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const fetchAllSubjects = async () => {
|
|
33
|
+
try {
|
|
34
|
+
// OLD WAY with axios and JWT
|
|
35
|
+
// const response = await axios({
|
|
36
|
+
// method: "get",
|
|
37
|
+
// url: `${baseURL}api/showAllSubjects`,
|
|
38
|
+
// headers: { Authorization: `Bearer ${token}` },
|
|
39
|
+
// });
|
|
40
|
+
|
|
41
|
+
// NEW WAY with apiClient
|
|
42
|
+
const data = await apiClient.get('/api/showAllSubjects');
|
|
43
|
+
setSubjects(data);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error fetching subjects:', error);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const fetchExamPacks = async (subjectId) => {
|
|
50
|
+
try {
|
|
51
|
+
// OLD WAY
|
|
52
|
+
// const response = await axios({
|
|
53
|
+
// method: "get",
|
|
54
|
+
// params: { subjectId },
|
|
55
|
+
// url: `${baseURL}api/getAllExamPacks`,
|
|
56
|
+
// headers: { Authorization: `Bearer ${token}` },
|
|
57
|
+
// });
|
|
58
|
+
|
|
59
|
+
// NEW WAY
|
|
60
|
+
const data = await apiClient.get(`/api/getAllExamPacks?subjectId=${subjectId}`);
|
|
61
|
+
setExamPacks(data);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error fetching exam packs:', error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const fetchExams = async (examPackId) => {
|
|
68
|
+
try {
|
|
69
|
+
const data = await apiClient.get(`/api/getAllExams?examPackId=${examPackId}`);
|
|
70
|
+
setExams(data);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Error fetching exams:', error);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const fetchUsers = async (examId) => {
|
|
77
|
+
try {
|
|
78
|
+
const data = await apiClient.get(`/api/getAllUsers?examId=${examId}`);
|
|
79
|
+
setUsers(data);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('Error fetching users:', error);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleSubjectChange = (event, newValue) => {
|
|
86
|
+
setSelectedSubject(newValue);
|
|
87
|
+
setSelectedExamPack(null);
|
|
88
|
+
setSelectedExam(null);
|
|
89
|
+
setSelectedUser(null);
|
|
90
|
+
setExamPacks([]);
|
|
91
|
+
setExams([]);
|
|
92
|
+
setUsers([]);
|
|
93
|
+
if (newValue) {
|
|
94
|
+
fetchExamPacks(newValue.subjectId);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleExamPackChange = (event, newValue) => {
|
|
99
|
+
setSelectedExamPack(newValue);
|
|
100
|
+
setSelectedExam(null);
|
|
101
|
+
setSelectedUser(null);
|
|
102
|
+
setExams([]);
|
|
103
|
+
setUsers([]);
|
|
104
|
+
if (newValue) {
|
|
105
|
+
fetchExams(newValue.id);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleExamChange = (event, newValue) => {
|
|
110
|
+
setSelectedExam(newValue);
|
|
111
|
+
setSelectedUser(null);
|
|
112
|
+
setUsers([]);
|
|
113
|
+
if (newValue) {
|
|
114
|
+
fetchUsers(newValue.id);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const handleUserChange = (event, newValue) => {
|
|
119
|
+
setSelectedUser(newValue);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleDownload = async () => {
|
|
123
|
+
setLoading(true);
|
|
124
|
+
try {
|
|
125
|
+
// OLD WAY with axios
|
|
126
|
+
// const url = `${baseURL}api/generateReportForSingleUser?subjectId=${selectedSubject.subjectId}&examPackId=${selectedExamPack.id}&examId=${selectedExam.id}&userId=${selectedUser.id}&wrongAnswerFlag=${onlyWrongAnswers}`;
|
|
127
|
+
// const response = await axios.get(url, {
|
|
128
|
+
// responseType: 'blob',
|
|
129
|
+
// headers: { Authorization: `Bearer ${token}` }
|
|
130
|
+
// });
|
|
131
|
+
|
|
132
|
+
// NEW WAY - need blob response
|
|
133
|
+
const response = await fetch(`${baseURL}api/generateReportForSingleUser?subjectId=${selectedSubject.subjectId}&examPackId=${selectedExamPack.id}&examId=${selectedExam.id}&userId=${selectedUser.id}&wrongAnswerFlag=${onlyWrongAnswers}`, {
|
|
134
|
+
headers: {
|
|
135
|
+
'X-Tenant-Token': apiClient.tenantToken
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const blob = await response.blob();
|
|
140
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
141
|
+
const link = document.createElement('a');
|
|
142
|
+
link.href = blobUrl;
|
|
143
|
+
link.setAttribute('download', 'Report.xlsx');
|
|
144
|
+
document.body.appendChild(link);
|
|
145
|
+
link.click();
|
|
146
|
+
link.parentNode.removeChild(link);
|
|
147
|
+
URL.revokeObjectURL(blobUrl);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Error downloading report:', error);
|
|
150
|
+
} finally {
|
|
151
|
+
setLoading(false);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div>
|
|
157
|
+
<Header />
|
|
158
|
+
<Sidebar />
|
|
159
|
+
<div style={{ padding: "100px 0px 0px 100px", marginLeft: "140px" }}>
|
|
160
|
+
<Box sx={{ maxWidth: 800, mx: 'auto', my: 4, p: 3, backgroundColor: '#f5f5f5', borderRadius: 2 }}>
|
|
161
|
+
<Paper elevation={6} sx={{ p: 5, textAlign: 'center', backgroundColor: '#fff' }}>
|
|
162
|
+
<Typography variant="h4" component="h1" gutterBottom sx={{ color: '#3f51b5', marginBottom: "20px" }}>
|
|
163
|
+
Exam Report
|
|
164
|
+
</Typography>
|
|
165
|
+
<Grid container spacing={2} justifyContent="center">
|
|
166
|
+
<Grid item xs={12} sm={6}>
|
|
167
|
+
<FormControl fullWidth>
|
|
168
|
+
<Autocomplete
|
|
169
|
+
id="ddlSubject"
|
|
170
|
+
options={subjects}
|
|
171
|
+
getOptionLabel={(option) => option.subjectName}
|
|
172
|
+
value={selectedSubject}
|
|
173
|
+
onChange={handleSubjectChange}
|
|
174
|
+
renderInput={(params) => <TextField {...params} label="Subject" />}
|
|
175
|
+
/>
|
|
176
|
+
</FormControl>
|
|
177
|
+
</Grid>
|
|
178
|
+
|
|
179
|
+
<Grid item xs={12} sm={6}>
|
|
180
|
+
<FormControl fullWidth>
|
|
181
|
+
<Autocomplete
|
|
182
|
+
id="ddlExamPack"
|
|
183
|
+
options={examPacks}
|
|
184
|
+
getOptionLabel={(option) => option.name}
|
|
185
|
+
value={selectedExamPack}
|
|
186
|
+
onChange={handleExamPackChange}
|
|
187
|
+
renderInput={(params) => <TextField {...params} label="Exam Pack" />}
|
|
188
|
+
/>
|
|
189
|
+
</FormControl>
|
|
190
|
+
</Grid>
|
|
191
|
+
|
|
192
|
+
<Grid item xs={12} sm={6}>
|
|
193
|
+
<FormControl fullWidth>
|
|
194
|
+
<Autocomplete
|
|
195
|
+
id="ddlExam"
|
|
196
|
+
options={exams}
|
|
197
|
+
getOptionLabel={(option) => option.examName}
|
|
198
|
+
value={selectedExam}
|
|
199
|
+
onChange={handleExamChange}
|
|
200
|
+
renderInput={(params) => <TextField {...params} label="Exam" />}
|
|
201
|
+
/>
|
|
202
|
+
</FormControl>
|
|
203
|
+
</Grid>
|
|
204
|
+
|
|
205
|
+
<Grid item xs={12} sm={6}>
|
|
206
|
+
<FormControl fullWidth>
|
|
207
|
+
<Autocomplete
|
|
208
|
+
id="ddlUser"
|
|
209
|
+
options={users}
|
|
210
|
+
getOptionLabel={(option) => option.userName}
|
|
211
|
+
value={selectedUser}
|
|
212
|
+
onChange={handleUserChange}
|
|
213
|
+
renderInput={(params) => <TextField {...params} label="User" />}
|
|
214
|
+
/>
|
|
215
|
+
</FormControl>
|
|
216
|
+
</Grid>
|
|
217
|
+
</Grid>
|
|
218
|
+
|
|
219
|
+
<Box sx={{ mt: 3, position: 'relative' }}>
|
|
220
|
+
<FormControlLabel
|
|
221
|
+
control={
|
|
222
|
+
<Checkbox
|
|
223
|
+
checked={onlyWrongAnswers}
|
|
224
|
+
onChange={(e) => setOnlyWrongAnswers(e.target.checked)}
|
|
225
|
+
/>
|
|
226
|
+
}
|
|
227
|
+
label="Only Wrong Answers"
|
|
228
|
+
/>
|
|
229
|
+
<Box sx={{ mt: 2, position: 'relative' }}>
|
|
230
|
+
<Button
|
|
231
|
+
variant="contained"
|
|
232
|
+
color="primary"
|
|
233
|
+
onClick={handleDownload}
|
|
234
|
+
disabled={!selectedSubject || !selectedExamPack || !selectedExam || !selectedUser || loading}
|
|
235
|
+
sx={{ width: 'auto', backgroundColor: '#3f51b5', '&:hover': { backgroundColor: '#303f9f' } }}
|
|
236
|
+
>
|
|
237
|
+
{loading ? 'Downloading...' : 'Download Report'}
|
|
238
|
+
</Button>
|
|
239
|
+
{loading && (
|
|
240
|
+
<CircularProgress
|
|
241
|
+
size={24}
|
|
242
|
+
sx={{
|
|
243
|
+
position: 'absolute',
|
|
244
|
+
top: '50%',
|
|
245
|
+
left: '50%',
|
|
246
|
+
marginTop: '-12px',
|
|
247
|
+
marginLeft: '-12px',
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
</Box>
|
|
252
|
+
</Box>
|
|
253
|
+
</Paper>
|
|
254
|
+
</Box>
|
|
255
|
+
|
|
256
|
+
<Snackbar
|
|
257
|
+
open={openAlert}
|
|
258
|
+
autoHideDuration={6000}
|
|
259
|
+
onClose={() => setOpenAlert(false)}
|
|
260
|
+
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
261
|
+
>
|
|
262
|
+
<Alert onClose={() => setOpenAlert(false)} severity="info" sx={{ width: '100%' }}>
|
|
263
|
+
{alertMessage}
|
|
264
|
+
</Alert>
|
|
265
|
+
</Snackbar>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export default Report;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class TenantApiClient {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.baseURL = config.baseURL;
|
|
4
|
+
this.tenantToken = config.tenantToken;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async request(endpoint, options = {}) {
|
|
8
|
+
// Remove double slashes
|
|
9
|
+
const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
|
|
10
|
+
const url = `${this.baseURL}/${cleanEndpoint}`;
|
|
11
|
+
console.log('FULL URL BEING SENT:', url); // Add this
|
|
12
|
+
|
|
13
|
+
const response = await fetch(url, {
|
|
14
|
+
...options,
|
|
15
|
+
headers: {
|
|
16
|
+
'X-Tenant-Token': this.tenantToken,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
...options.headers
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (response.status === 401) {
|
|
23
|
+
throw new Error('Invalid or missing tenant token');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return response.json();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Add your API methods
|
|
30
|
+
async get(endpoint) {
|
|
31
|
+
return this.request(endpoint);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async post(endpoint, data) {
|
|
35
|
+
return this.request(endpoint, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
body: JSON.stringify(data)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default TenantApiClient;
|