icleafreportui 0.1.0 → 0.1.2
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/dist/App.js +52 -0
- package/dist/Login.js +177 -0
- package/dist/Reports/CourseReport.js +243 -0
- package/dist/Reports/ExamPackReport.js +559 -0
- package/dist/Reports/ExamReport.js +290 -0
- package/dist/Reports/Report.js +299 -0
- package/dist/api/client.js +44 -0
- package/dist/components/Header.js +147 -0
- package/dist/components/Loader.js +30 -0
- package/dist/components/imagePathUrl.js +17 -0
- package/dist/components/sidebar.js +78 -0
- package/dist/context/TenantProvider.js +34 -0
- package/dist/index.js +19 -0
- package/dist/package.js +101 -0
- package/dist/reportWebVitals.js +27 -0
- package/dist/setupTests.js +3 -0
- package/package.json +11 -3
- package/.env +0 -7
- 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 +0 -45
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +0 -25
- package/public/robots.txt +0 -3
- package/src/App.js +0 -37
- package/src/Login.js +0 -159
- package/src/Reports/CourseReport.js +0 -209
- package/src/Reports/ExamPackReport.js +0 -554
- package/src/Reports/ExamReport.js +0 -269
- package/src/Reports/Report.js +0 -271
- package/src/api/client.jsx +0 -42
- package/src/components/Header.jsx +0 -192
- package/src/components/Loader.jsx +0 -23
- package/src/components/imagePathUrl.jsx +0 -11
- package/src/components/sidebar.jsx +0 -81
- package/src/context/TenantProvider.jsx +0 -22
- package/src/index.js +0 -21
- package/src/package.js +0 -10
- package/src/reportWebVitals.js +0 -13
- package/src/setupTests.js +0 -5
- /package/{src → dist}/App.css +0 -0
- /package/{src → dist}/components/Header.css +0 -0
- /package/{src → dist}/components/sidebar.css +0 -0
- /package/{src → dist}/fonts/210000.jpg +0 -0
- /package/{src → dist}/fonts/210001.jpg +0 -0
- /package/{src → dist}/fonts/210003.jpg +0 -0
- /package/{src → dist}/fonts/210004.jpg +0 -0
- /package/{src → dist}/fonts/210006.jpg +0 -0
- /package/{src → dist}/fonts/210018.jpg +0 -0
- /package/{src → dist}/fonts/210019.jpg +0 -0
- /package/{src → dist}/fonts/210020.jpg +0 -0
- /package/{src → dist}/fonts/210279.jpg +0 -0
- /package/{src → dist}/fonts/210280.jpg +0 -0
- /package/{src → dist}/fonts/Gilroy-Black.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-BlackItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-Bold.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-BoldItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-ExtraBold.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-ExtraBoldItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-Heavy.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-HeavyItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-Light.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-LightItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-Medium.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-MediumItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-Regular.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-RegularItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-SemiBold.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-SemiBoldItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-Thin.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-ThinItalic.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-UltraLight.ttf +0 -0
- /package/{src → dist}/fonts/Gilroy-UltraLightItalic.ttf +0 -0
- /package/{src → dist}/fonts/Help - Guide Document.pdf +0 -0
- /package/{src → dist}/fonts/License.txt +0 -0
- /package/{src → dist}/fonts/More Free Fonts on fontshmonts.com.url +0 -0
- /package/{src → dist}/fonts/cover.jpg +0 -0
- /package/{src → dist}/index.css +0 -0
- /package/{src → dist}/login.css +0 -0
- /package/{src → dist}/logo.svg +0 -0
- /package/{src → dist}/styles.css +0 -0
- /package/{src → dist}/theme.css +0 -0
package/src/App.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import Login from './Login';
|
|
3
|
-
import Report from './Reports/Report';
|
|
4
|
-
import { HashRouter, Route, Routes } from 'react-router-dom';
|
|
5
|
-
import CourseReport from './Reports/CourseReport';
|
|
6
|
-
import ExamReport from './Reports/ExamReport';
|
|
7
|
-
import ExamPackReport from './Reports/ExamPackReport';
|
|
8
|
-
import { TenantProvider } from './context/TenantProvider'; // ADD THIS
|
|
9
|
-
|
|
10
|
-
const App = () => {
|
|
11
|
-
const userToken = localStorage.getItem("token")
|
|
12
|
-
|
|
13
|
-
// Configuration for API calls
|
|
14
|
-
const config = {
|
|
15
|
-
baseURL: process.env.REACT_APP_BASE_URL || 'http://192.168.2.203:8080/icleafreport',
|
|
16
|
-
tenantToken: 'tn_852cf0d3242a11f19d84005056575b50' // Your test token
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<TenantProvider config={config}>
|
|
21
|
-
<div className="App">
|
|
22
|
-
<HashRouter>
|
|
23
|
-
<Routes>
|
|
24
|
-
<Route path="/" element={userToken != null ? <Report /> : <Login />} />
|
|
25
|
-
<Route path="/login" element={userToken != null ? <Report /> : <Login />} />
|
|
26
|
-
<Route path="/report" element={<Report />} />
|
|
27
|
-
<Route path="/coursereport" element={<CourseReport />} />
|
|
28
|
-
<Route path="/examReport" element={<ExamReport />} />
|
|
29
|
-
<Route path="/examPackReport" element={<ExamPackReport />} />
|
|
30
|
-
</Routes>
|
|
31
|
-
</HashRouter>
|
|
32
|
-
</div>
|
|
33
|
-
</TenantProvider>
|
|
34
|
-
);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export default App;
|
package/src/Login.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import { useHistory, useNavigate } from 'react-router-dom'; // Correct import statement for useHistory
|
|
4
|
-
import { logo, youngMan } from './components/imagePathUrl';
|
|
5
|
-
import { RiMailLine, RiLockLine, RiEyeOffLine } from 'react-icons/ri';
|
|
6
|
-
import { AiOutlineEyeInvisible, AiOutlineEye } from 'react-icons/ai';
|
|
7
|
-
import 'bootstrap/dist/css/bootstrap.css';
|
|
8
|
-
import './login.css';
|
|
9
|
-
import { Alert, Button, Snackbar } from '@mui/material';
|
|
10
|
-
|
|
11
|
-
const Login = () => {
|
|
12
|
-
const [username, setUsername] = useState('');
|
|
13
|
-
const [password, setPassword] = useState('');
|
|
14
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
15
|
-
const [error, setError] = useState('');
|
|
16
|
-
const [openAlert, setOpenAlert] = useState(false);
|
|
17
|
-
const navigate = useNavigate(); // Initialize useHistory
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// const getSubDomain = () => {
|
|
21
|
-
// const hostname = window.location.hostname;
|
|
22
|
-
// const parts = hostname.split('.');
|
|
23
|
-
// if (parts.length > 2) {
|
|
24
|
-
// return parts [0];
|
|
25
|
-
// }
|
|
26
|
-
// }
|
|
27
|
-
|
|
28
|
-
const getSubDomain = () => {
|
|
29
|
-
const hostname = window.location.hostname;
|
|
30
|
-
console.log("Hostname:", hostname); // debug
|
|
31
|
-
|
|
32
|
-
// For localhost without subdomain
|
|
33
|
-
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
34
|
-
return "icleaf";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const parts = hostname.split('.');
|
|
38
|
-
if (parts.length > 2) {
|
|
39
|
-
return parts[0]; // Returns subdomain
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// If no subdomain found
|
|
43
|
-
return "icleaf";
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const baseURL = process.env.REACT_APP_BASE_URL;
|
|
47
|
-
|
|
48
|
-
const handleLogin = async (e) => {
|
|
49
|
-
e.preventDefault();
|
|
50
|
-
|
|
51
|
-
const subdomain = getSubDomain();
|
|
52
|
-
try {
|
|
53
|
-
// const response = await axios.get(`${baseURL}` + "api/login?userName=" + username + "&password=" + password);
|
|
54
|
-
const response = await axios.get(
|
|
55
|
-
`${baseURL}api/login?userName=${username}&password=${password}&subdomain=${subdomain}`
|
|
56
|
-
);
|
|
57
|
-
console.log(response, "response")
|
|
58
|
-
if (response.data) {
|
|
59
|
-
var res = response.data;
|
|
60
|
-
if (res == "User Not Found") {
|
|
61
|
-
setError(res);
|
|
62
|
-
setOpenAlert(true);
|
|
63
|
-
} else if (res == "UserName or Password cannot be empty") {
|
|
64
|
-
setError(res);
|
|
65
|
-
setOpenAlert(true);
|
|
66
|
-
} else {
|
|
67
|
-
localStorage.setItem("token", response.data);
|
|
68
|
-
localStorage.setItem("subdomain", subdomain);
|
|
69
|
-
navigate('/Report');
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
} catch (error) {
|
|
75
|
-
setError('User Not Found');
|
|
76
|
-
setOpenAlert(true);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const togglePasswordVisibility = () => {
|
|
81
|
-
setShowPassword(!showPassword);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<>
|
|
86
|
-
<div className="row page_container">
|
|
87
|
-
<div className="col-6 login_container1">
|
|
88
|
-
<img className='login_img' src={youngMan} />
|
|
89
|
-
</div>
|
|
90
|
-
<div className="col-6 login_container2_bg">
|
|
91
|
-
<div className='login_form_container'>
|
|
92
|
-
<div className='logo_container'>
|
|
93
|
-
<img className='logo_img' src={logo} alt="Login logo Image" />
|
|
94
|
-
</div>
|
|
95
|
-
<text className="welcome_text">Welcome</text>
|
|
96
|
-
<form onSubmit={handleLogin}>
|
|
97
|
-
<label className='login_label'>Username</label>
|
|
98
|
-
<div className='input_contanier'>
|
|
99
|
-
<div className="input_icons">
|
|
100
|
-
<RiMailLine />
|
|
101
|
-
</div>
|
|
102
|
-
<input
|
|
103
|
-
type="username"
|
|
104
|
-
id="username"
|
|
105
|
-
name="username"
|
|
106
|
-
className='input_box'
|
|
107
|
-
placeholder="Username"
|
|
108
|
-
// onBlur={(e) => validateForm(e.target.value, language.enterUsername, "userName")}
|
|
109
|
-
value={username}
|
|
110
|
-
onChange={(e) => {
|
|
111
|
-
setUsername(e.target.value);
|
|
112
|
-
// validateForm(e.target.value, language.enterUsername, "userName")
|
|
113
|
-
}}
|
|
114
|
-
/>
|
|
115
|
-
</div>
|
|
116
|
-
<label className='login_label'>Password</label>
|
|
117
|
-
<div className='input_contanier'>
|
|
118
|
-
<div className="input_icons">
|
|
119
|
-
<RiLockLine />
|
|
120
|
-
</div>
|
|
121
|
-
<input
|
|
122
|
-
type={showPassword ? "text" : "password"}
|
|
123
|
-
id="password"
|
|
124
|
-
name="password"
|
|
125
|
-
className='input_box'
|
|
126
|
-
// onBlur={(e) => validateForm(e.target.value, language.enterPass, "password")}
|
|
127
|
-
placeholder="Password"
|
|
128
|
-
value={password}
|
|
129
|
-
onChange={(e) => {
|
|
130
|
-
setPassword(e.target.value);
|
|
131
|
-
// validateForm(e.target.value, language.enterPass, "password")
|
|
132
|
-
}}
|
|
133
|
-
style={{ userSelect: "none" }}
|
|
134
|
-
/>
|
|
135
|
-
{showPassword ? <AiOutlineEye onClick={() => togglePasswordVisibility()} className="eye-icon_login" /> : <AiOutlineEyeInvisible onClick={() => togglePasswordVisibility()} className="eye-icon_login" />}
|
|
136
|
-
</div>
|
|
137
|
-
<div className='login_btn_container'>
|
|
138
|
-
<Button variant="contained" style={{ width: "100%", marginTop: "15px", padding: "10px" }} type="submit">Login</Button>
|
|
139
|
-
</div>
|
|
140
|
-
</form>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<Snackbar
|
|
145
|
-
open={openAlert}
|
|
146
|
-
autoHideDuration={6000}
|
|
147
|
-
onClose={() => setOpenAlert(false)}
|
|
148
|
-
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
149
|
-
>
|
|
150
|
-
<Alert onClose={() => setOpenAlert(false)} severity="info" sx={{ width: '30%' }}>
|
|
151
|
-
{error}
|
|
152
|
-
</Alert>
|
|
153
|
-
</Snackbar>
|
|
154
|
-
</div>
|
|
155
|
-
</>
|
|
156
|
-
);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export default Login;
|
|
@@ -1,209 +0,0 @@
|
|
|
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 } 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 CourseReport() {
|
|
10
|
-
const [token, setToken] = useState(localStorage.getItem("token"));
|
|
11
|
-
const navigate = useNavigate();
|
|
12
|
-
const [subjects, setSubjects] = useState([]);
|
|
13
|
-
const [courses, setCourses] = useState([]);
|
|
14
|
-
const [selectedSubject, setSelectedSubject] = useState(null);
|
|
15
|
-
const [selectedCourse, setSelectedCourse] = useState(null);
|
|
16
|
-
const [reportTypes] = useState(['Summary', 'Daywise']);
|
|
17
|
-
const [selectedReportType, setSelectedReportType] = useState(null);
|
|
18
|
-
const [alertMessage, setAlertMessage] = useState('');
|
|
19
|
-
const [loading, setLoading] = useState(false);
|
|
20
|
-
const [alertOpen, setAlertOpen] = useState(false);
|
|
21
|
-
|
|
22
|
-
const baseURL = process.env.REACT_APP_BASE_URL
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (token == null) {
|
|
26
|
-
navigate("/login");
|
|
27
|
-
} else {
|
|
28
|
-
fetchAllSubjects();
|
|
29
|
-
}
|
|
30
|
-
}, []);
|
|
31
|
-
|
|
32
|
-
const fetchAllSubjects = async () => {
|
|
33
|
-
try {
|
|
34
|
-
const response = await axios({
|
|
35
|
-
method: "get",
|
|
36
|
-
url: `${baseURL}api/showAllSubjects`,
|
|
37
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
38
|
-
});
|
|
39
|
-
setSubjects(response.data);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error('Error fetching subjects:', error);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const fetchCourses = async (subjectId) => {
|
|
46
|
-
try {
|
|
47
|
-
const response = await axios({
|
|
48
|
-
method: "get",
|
|
49
|
-
params: { subjectId },
|
|
50
|
-
url: `${baseURL}api/getCoursesBySubject`,
|
|
51
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
52
|
-
});
|
|
53
|
-
setCourses(response.data);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error('Error fetching courses:', error);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const handleSubjectChange = (event, newValue) => {
|
|
60
|
-
setSelectedSubject(newValue);
|
|
61
|
-
setSelectedCourse(null);
|
|
62
|
-
setCourses([]);
|
|
63
|
-
if (newValue) {
|
|
64
|
-
fetchCourses(newValue.subjectId);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleCourseChange = (event, newValue) => {
|
|
69
|
-
setSelectedCourse(newValue);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const handleReportTypeChange = (event, newValue) => {
|
|
73
|
-
setSelectedReportType(newValue);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const handleDownload = async () => {
|
|
77
|
-
setLoading(true);
|
|
78
|
-
try {
|
|
79
|
-
let url;
|
|
80
|
-
if (selectedReportType === 'Summary') {
|
|
81
|
-
url = `${baseURL}api/downloadUserCorpCourseExcel?corpCourseId=${selectedCourse.id}`;
|
|
82
|
-
} else if (selectedReportType === 'Daywise') {
|
|
83
|
-
url = `${baseURL}api/downloadUserCorpCourseExcelDayWise?corpCourseId=${selectedCourse.id}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const response = await axios.get(url, {
|
|
87
|
-
responseType: 'blob',
|
|
88
|
-
headers: {
|
|
89
|
-
Authorization: `Bearer ${token}`
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
const contentDisposition = response.headers['content-disposition'];
|
|
93
|
-
const text = await response.data.text();
|
|
94
|
-
if (text.includes('Course not configured')) {
|
|
95
|
-
setAlertMessage('Course not configured.');
|
|
96
|
-
setAlertOpen(true);
|
|
97
|
-
} else {
|
|
98
|
-
const filename = 'course_report.xlsx';
|
|
99
|
-
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
100
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
101
|
-
const link = document.createElement('a');
|
|
102
|
-
link.href = blobUrl;
|
|
103
|
-
link.setAttribute('download', filename);
|
|
104
|
-
document.body.appendChild(link);
|
|
105
|
-
link.click();
|
|
106
|
-
link.parentNode.removeChild(link);
|
|
107
|
-
URL.revokeObjectURL(blobUrl);
|
|
108
|
-
}
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error('Error downloading course report:', error);
|
|
111
|
-
} finally {
|
|
112
|
-
setLoading(false);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<div>
|
|
119
|
-
<Header />
|
|
120
|
-
<Sidebar />
|
|
121
|
-
<div style={{ padding: "100px 0px 0px 100px", marginLeft: "140px" }}>
|
|
122
|
-
<Box sx={{ maxWidth: 800, mx: 'auto', my: 4, p: 3, backgroundColor: '#f5f5f5', borderRadius: 2 }}>
|
|
123
|
-
<Paper elevation={6} sx={{ p: 5, textAlign: 'center', backgroundColor: '#fff' }}>
|
|
124
|
-
<Typography variant="h4" component="h1" gutterBottom sx={{ color: '#3f51b5', marginBottom: "20px" }}>
|
|
125
|
-
Course Report
|
|
126
|
-
</Typography>
|
|
127
|
-
<Grid container spacing={2} justifyContent="center">
|
|
128
|
-
<Grid item xs={12} sm={4}>
|
|
129
|
-
<FormControl fullWidth>
|
|
130
|
-
<Autocomplete
|
|
131
|
-
id="ddlSubject"
|
|
132
|
-
options={subjects}
|
|
133
|
-
getOptionLabel={(option) => option.subjectName}
|
|
134
|
-
value={selectedSubject}
|
|
135
|
-
onChange={handleSubjectChange}
|
|
136
|
-
renderInput={(params) => <TextField {...params} label="Subject" />}
|
|
137
|
-
/>
|
|
138
|
-
</FormControl>
|
|
139
|
-
</Grid>
|
|
140
|
-
|
|
141
|
-
<Grid item xs={12} sm={4}>
|
|
142
|
-
<FormControl fullWidth>
|
|
143
|
-
<Autocomplete
|
|
144
|
-
id="ddlCourse"
|
|
145
|
-
options={courses}
|
|
146
|
-
getOptionLabel={(option) => option.courseName}
|
|
147
|
-
value={selectedCourse}
|
|
148
|
-
onChange={handleCourseChange}
|
|
149
|
-
renderInput={(params) => <TextField {...params} label="Course" />}
|
|
150
|
-
/>
|
|
151
|
-
</FormControl>
|
|
152
|
-
</Grid>
|
|
153
|
-
|
|
154
|
-
<Grid item xs={12} sm={4}>
|
|
155
|
-
<FormControl fullWidth>
|
|
156
|
-
<Autocomplete
|
|
157
|
-
id="ddlReportType"
|
|
158
|
-
options={reportTypes}
|
|
159
|
-
getOptionLabel={(option) => option}
|
|
160
|
-
value={selectedReportType}
|
|
161
|
-
onChange={handleReportTypeChange}
|
|
162
|
-
renderInput={(params) => <TextField {...params} label="Report Type" />}
|
|
163
|
-
/>
|
|
164
|
-
</FormControl>
|
|
165
|
-
</Grid>
|
|
166
|
-
</Grid>
|
|
167
|
-
|
|
168
|
-
<Box sx={{ mt: 3, position: 'relative' }}>
|
|
169
|
-
<Button
|
|
170
|
-
variant="contained"
|
|
171
|
-
color="primary"
|
|
172
|
-
onClick={handleDownload}
|
|
173
|
-
disabled={!selectedSubject || !selectedCourse || !selectedReportType || loading}
|
|
174
|
-
sx={{ width: 'auto', backgroundColor: '#3f51b5', '&:hover': { backgroundColor: '#303f9f' } }}
|
|
175
|
-
>
|
|
176
|
-
{loading ? 'Downloading...' : 'Download Report'}
|
|
177
|
-
</Button>
|
|
178
|
-
{loading && (
|
|
179
|
-
<CircularProgress
|
|
180
|
-
size={24}
|
|
181
|
-
sx={{
|
|
182
|
-
position: 'absolute',
|
|
183
|
-
top: '50%',
|
|
184
|
-
left: '50%',
|
|
185
|
-
marginTop: '-12px',
|
|
186
|
-
marginLeft: '-12px',
|
|
187
|
-
}}
|
|
188
|
-
/>
|
|
189
|
-
)}
|
|
190
|
-
</Box>
|
|
191
|
-
</Paper>
|
|
192
|
-
</Box>
|
|
193
|
-
|
|
194
|
-
<Snackbar
|
|
195
|
-
open={alertOpen}
|
|
196
|
-
autoHideDuration={6000}
|
|
197
|
-
onClose={() => setAlertOpen(false)}
|
|
198
|
-
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
199
|
-
>
|
|
200
|
-
<Alert onClose={() => setAlertOpen(false)} severity="info" sx={{ width: '100%' }}>
|
|
201
|
-
{alertMessage}
|
|
202
|
-
</Alert>
|
|
203
|
-
</Snackbar>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export default CourseReport;
|