summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app/AuthenticatedApp.js41
-rw-r--r--src/app/ProfessorRoutes.js22
-rw-r--r--src/app/StudentRoutes.js31
-rw-r--r--src/app/data.js1
-rw-r--r--src/components/AssignmentCard/index.js47
-rw-r--r--src/components/ClassCard/index.js75
-rw-r--r--src/components/FormDialog/index.js33
-rw-r--r--src/components/PublishAnnouncementCard/index.js64
-rw-r--r--src/components/PublishAnnouncementCard/styles.js47
-rw-r--r--src/context/auth.js12
-rw-r--r--src/context/user.js85
-rw-r--r--src/screens/Assignment/index.js6
-rw-r--r--src/screens/Information/index.js6
-rw-r--r--src/screens/professor/Classroom/AnnouncementsTab/index.js525
-rw-r--r--src/screens/professor/Classroom/AnnouncementsTab/styles.js49
-rw-r--r--src/screens/professor/Classroom/AssignmentsTab/index.js409
-rw-r--r--src/screens/professor/Classroom/AssignmentsTab/styles.js138
-rw-r--r--src/screens/professor/Classroom/GradesTab/index.js34
-rw-r--r--src/screens/professor/Classroom/GradesTab/styles.js0
-rw-r--r--src/screens/professor/Classroom/Header/index.js62
-rw-r--r--src/screens/professor/Classroom/Header/styles.js90
-rw-r--r--src/screens/professor/Classroom/PeopleTab/index.js (renamed from src/screens/Classroom/PeopleTab/index.js)2
-rw-r--r--src/screens/professor/Classroom/PeopleTab/styles.js (renamed from src/screens/Classroom/PeopleTab/styles.js)0
-rw-r--r--src/screens/professor/Classroom/View.js51
-rw-r--r--src/screens/professor/Classroom/index.js156
-rw-r--r--src/screens/professor/Classroom/styles.js (renamed from src/screens/Classroom/styles.js)0
-rw-r--r--src/screens/professor/Classroom/tabOptions.js20
-rw-r--r--src/screens/professor/Home/View.js200
-rw-r--r--src/screens/professor/Home/index.js51
-rw-r--r--src/screens/professor/Home/styles.js42
-rw-r--r--src/screens/student/Classroom/AnnouncementsTab/index.js (renamed from src/screens/Classroom/AnnouncementsTab/index.js)12
-rw-r--r--src/screens/student/Classroom/AnnouncementsTab/styles.js (renamed from src/screens/Classroom/AnnouncementsTab/styles.js)0
-rw-r--r--src/screens/student/Classroom/AssignmentsTab/index.js (renamed from src/screens/Classroom/AssignmentsTab/index.js)2
-rw-r--r--src/screens/student/Classroom/AssignmentsTab/styles.js (renamed from src/screens/Classroom/AssignmentsTab/styles.js)0
-rw-r--r--src/screens/student/Classroom/Header/index.js (renamed from src/screens/Classroom/Header/index.js)0
-rw-r--r--src/screens/student/Classroom/Header/styles.js (renamed from src/screens/Classroom/Header/styles.js)0
-rw-r--r--src/screens/student/Classroom/PeopleTab/index.js244
-rw-r--r--src/screens/student/Classroom/PeopleTab/styles.js116
-rw-r--r--src/screens/student/Classroom/View.js (renamed from src/screens/Classroom/View.js)0
-rw-r--r--src/screens/student/Classroom/index.js (renamed from src/screens/Classroom/index.js)41
-rw-r--r--src/screens/student/Classroom/styles.js34
-rw-r--r--src/screens/student/Classroom/tabOptions.js (renamed from src/screens/Classroom/tabOptions.js)0
-rw-r--r--src/screens/student/Home/View.js (renamed from src/screens/Home/View.js)6
-rw-r--r--src/screens/student/Home/index.js (renamed from src/screens/Home/index.js)16
-rw-r--r--src/screens/student/Home/styles.js (renamed from src/screens/Home/styles.js)0
-rw-r--r--src/services/professor.js27
-rw-r--r--src/services/provider.js31
-rw-r--r--src/services/student.js29
-rw-r--r--src/services/user-service.js120
-rw-r--r--src/utils/mocks/api.js147
-rw-r--r--src/utils/mocks/responses.js (renamed from src/services/mocks.js)151
51 files changed, 2962 insertions, 313 deletions
diff --git a/src/app/AuthenticatedApp.js b/src/app/AuthenticatedApp.js
index 80d66be..b4a9412 100644
--- a/src/app/AuthenticatedApp.js
+++ b/src/app/AuthenticatedApp.js
@@ -1,22 +1,20 @@
-import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
+import { lazy } from 'react';
+import { useNavigate } from 'react-router-dom';
import { Container } from '@mui/system';
-import { useUser } from '../context/user';
import { useAuthState } from '../context/auth';
+import { useUser } from '../context/user';
import MainMenu from '../components/MainMenu';
-import Home from '../screens/Home';
-import Information from '../screens/Information';
-import Calendar from '../screens/Calendar';
import useLayoutType from '../hooks/useLayoutType';
import Toolbar from '../components/Toolbar';
-import Classroom from '../screens/Classroom';
-import Assignment from '../screens/Assignment';
-import Profile from '../screens/Profile';
import { avatarMenuOptions, menuOptions } from './data';
import styles from './styles';
+const StudentRoutes = lazy(() => import('./StudentRoutes'));
+const ProfessorRoutes = lazy(() => import('./ProfessorRoutes'));
+
function AuthenticatedApp() {
const navigate = useNavigate();
const { state } = useUser();
@@ -24,6 +22,17 @@ function AuthenticatedApp() {
const layoutType = useLayoutType();
const { container, toolbar } = styles[layoutType];
+ const routeResolver = role => {
+ switch (role) {
+ case 'STUDENT':
+ return <StudentRoutes />;
+ case 'PROFESSOR':
+ return <ProfessorRoutes />;
+ default:
+ return null;
+ }
+ };
+
return (
state &&
state.user && (
@@ -43,21 +52,7 @@ function AuthenticatedApp() {
options={menuOptions(state.pathname)}
layoutType={layoutType}
/>
- <Routes>
- <Route path="/home" element={<Home />} />
- <Route path="/info" element={<Information />} />
- <Route path="/calendar" element={<Calendar />} />
- <Route path="/profile" element={<Profile />} />
- <Route path="/class">
- <Route path=":id" element={<Classroom />} />
- </Route>
- <Route path="/assignment">
- <Route path=":id" element={<Assignment />} />
- </Route>
- <Route path="/login" element={<Navigate to="/home" />} />
- <Route path="/register" element={<Navigate to="/home" />} />
- <Route path="/" element={<Navigate to="/home" />} />
- </Routes>
+ {routeResolver(state.user.role)}
</Container>
</>
)
diff --git a/src/app/ProfessorRoutes.js b/src/app/ProfessorRoutes.js
new file mode 100644
index 0000000..ced0d15
--- /dev/null
+++ b/src/app/ProfessorRoutes.js
@@ -0,0 +1,22 @@
+import { Navigate, Route, Routes } from 'react-router-dom';
+import Classroom from '../screens/professor/Classroom';
+import Home from '../screens/professor/Home';
+
+function ProfessorRoutes() {
+ return (
+ <Routes>
+ <Route path="/calendar" element={<h1>Calendar</h1>} />
+ <Route path="/profile" element={<h1>Profile</h1>} />
+ <Route path="/class">
+ <Route path=":id" element={<Classroom />} />
+ </Route>
+ <Route path="/info" element={<h1>Information</h1>} />
+ <Route path="/home" element={<Home />} />
+ <Route path="/login" element={<Navigate to="/home" />} />
+ <Route path="/register" element={<Navigate to="/home" />} />
+ <Route path="/" element={<Navigate to="/home" />} />
+ </Routes>
+ );
+}
+
+export default ProfessorRoutes;
diff --git a/src/app/StudentRoutes.js b/src/app/StudentRoutes.js
new file mode 100644
index 0000000..1dc6b4a
--- /dev/null
+++ b/src/app/StudentRoutes.js
@@ -0,0 +1,31 @@
+import { Navigate, Route, Routes } from 'react-router-dom';
+
+import Home from '../screens/student/Home';
+import Classroom from '../screens/student/Classroom';
+
+import Information from '../screens/Information';
+import Calendar from '../screens/Calendar';
+import Assignment from '../screens/Assignment';
+import Profile from '../screens/Profile';
+
+function StudentRoutes() {
+ return (
+ <Routes>
+ <Route path="/home" element={<Home />} />
+ <Route path="/info" element={<Information />} />
+ <Route path="/calendar" element={<Calendar />} />
+ <Route path="/profile" element={<Profile />} />
+ <Route path="/class">
+ <Route path=":id" element={<Classroom />} />
+ </Route>
+ <Route path="/assignment">
+ <Route path=":id" element={<Assignment />} />
+ </Route>
+ <Route path="/login" element={<Navigate to="/home" />} />
+ <Route path="/register" element={<Navigate to="/home" />} />
+ <Route path="/" element={<Navigate to="/home" />} />
+ </Routes>
+ );
+}
+
+export default StudentRoutes;
diff --git a/src/app/data.js b/src/app/data.js
index e7048f5..647eb01 100644
--- a/src/app/data.js
+++ b/src/app/data.js
@@ -28,6 +28,7 @@ const menuOptions = activePath => [
isActive:
activePath === '/home' ||
activePath === '/login' ||
+ activePath === '/register' ||
activePath === '/profile' ||
activePath === '/' ||
activePath.indexOf('class') !== -1 ||
diff --git a/src/components/AssignmentCard/index.js b/src/components/AssignmentCard/index.js
index e893b3d..fc5758b 100644
--- a/src/components/AssignmentCard/index.js
+++ b/src/components/AssignmentCard/index.js
@@ -18,6 +18,10 @@ function AssignmentCard({
classrooms,
dueDate,
scores,
+ deliveredByStudents,
+ reviewed,
+ total,
+ isAssignmentToReview,
layoutType,
onClick,
}) {
@@ -61,16 +65,30 @@ function AssignmentCard({
{classrooms.map(c => c.name).join(', ')}
</Typography>
<Divider sx={dividerMiddle} />
+
<Typography sx={typographyDueDate} variant="p" component="div">
<strong>Data de entrega: </strong>{' '}
{capitalizeFirstLetter(
dayjs(dueDate).format('dddd, DD/MM | HH:mm[h]')
)}
</Typography>
- <Typography variant="p" component="div">
- <strong>Valor: </strong>
- {scores.map(s => s.value).join(', ')} pts
- </Typography>
+ {deliveredByStudents >= 0 && total && (
+ <Typography variant="p" component="div">
+ <strong>Entregues: </strong>{' '}
+ {`${deliveredByStudents} de ${total}`}
+ </Typography>
+ )}
+ {reviewed >= 0 && total && (
+ <Typography variant="p" component="div">
+ <strong>Corrigidas: </strong> {`${reviewed} de ${total}`}
+ </Typography>
+ )}
+ {!isAssignmentToReview && (
+ <Typography variant="p" component="div">
+ <strong>Valor: </strong>
+ {scores.map(s => s.value).join(', ')} pts
+ </Typography>
+ )}
</Stack>
</CardContent>
</CardActionArea>
@@ -110,10 +128,23 @@ function AssignmentCard({
dayjs(dueDate).format('dddd, DD/MM | HH:mm[h]')
)}
</Typography>
- <Typography variant="p" component="div">
- <strong>Valor: </strong>
- {scores.map(s => s.value).join(', ')} pts
- </Typography>
+ {deliveredByStudents >= 0 && total && (
+ <Typography variant="p" component="div">
+ <strong>Entregues: </strong>{' '}
+ {`${deliveredByStudents} de ${total}`}
+ </Typography>
+ )}
+ {reviewed >= 0 && total && (
+ <Typography variant="p" component="div">
+ <strong>Corrigidas: </strong> {`${reviewed} de ${total}`}
+ </Typography>
+ )}
+ {!isAssignmentToReview && (
+ <Typography variant="p" component="div">
+ <strong>Valor: </strong>
+ {scores.map(s => s.value).join(', ')} pts
+ </Typography>
+ )}
</Stack>
</CardContent>
</CardActionArea>
diff --git a/src/components/ClassCard/index.js b/src/components/ClassCard/index.js
index 79bf495..6c78254 100644
--- a/src/components/ClassCard/index.js
+++ b/src/components/ClassCard/index.js
@@ -17,6 +17,7 @@ function ClassCard({
title,
color,
teachers,
+ course,
layoutType,
onClick,
}) {
@@ -45,23 +46,30 @@ function ClassCard({
>
{title}
</Typography>
- <Stack alignItems="center" direction="row" spacing={1}>
- <AvatarGroup total={teachers.length}>
- {teachers.map(t => (
- <Avatar
- key={t.name}
- alt={t.name}
- src={t.avatar}
- sx={avatar}
- />
- ))}
- </AvatarGroup>
- <Tooltip title={teachers.map(t => t.name).join(', ')}>
- <Typography sx={tooltip} variant="body3" color="text.secondary">
- {teachers.map(t => t.name).join(', ')}
- </Typography>
- </Tooltip>
- </Stack>
+ {teachers && (
+ <Stack alignItems="center" direction="row" spacing={1}>
+ <AvatarGroup total={teachers.length}>
+ {teachers.map(t => (
+ <Avatar
+ key={t.name}
+ alt={t.name}
+ src={t.avatar}
+ sx={avatar}
+ />
+ ))}
+ </AvatarGroup>
+ <Tooltip title={teachers.map(t => t.name).join(', ')}>
+ <Typography
+ sx={tooltip}
+ variant="body3"
+ color="text.secondary"
+ >
+ {teachers.map(t => t.name).join(', ')}
+ </Typography>
+ </Tooltip>
+ </Stack>
+ )}
+ {course && <Typography variant="body2">{course}</Typography>}
</CardContent>
</CardActionArea>
</Card>
@@ -82,21 +90,24 @@ function ClassCard({
>
{title}
</Typography>
- <Stack alignItems="center" direction="row" spacing={1}>
- <AvatarGroup total={teachers.length}>
- {teachers.map(t => (
- <Avatar
- key={t.name}
- alt={t.name}
- src={t.avatar}
- sx={avatar}
- />
- ))}
- </AvatarGroup>
- <Typography sx={tooltip} variant="body2" color="text.secondary">
- {teachers.map(t => t.name).join(', ')}
- </Typography>
- </Stack>
+ {teachers && (
+ <Stack alignItems="center" direction="row" spacing={1}>
+ <AvatarGroup total={teachers.length}>
+ {teachers.map(t => (
+ <Avatar
+ key={t.name}
+ alt={t.name}
+ src={t.avatar}
+ sx={avatar}
+ />
+ ))}
+ </AvatarGroup>
+ <Typography sx={tooltip} variant="body2" color="text.secondary">
+ {teachers.map(t => t.name).join(', ')}
+ </Typography>
+ </Stack>
+ )}
+ {course && <Typography variant="body2">{course}</Typography>}
</CardContent>
</CardActionArea>
</Card>
diff --git a/src/components/FormDialog/index.js b/src/components/FormDialog/index.js
new file mode 100644
index 0000000..0b8ca30
--- /dev/null
+++ b/src/components/FormDialog/index.js
@@ -0,0 +1,33 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+} from '@mui/material';
+
+function FormDialog({
+ isOpened,
+ title,
+ contentText,
+ inputs,
+ onDismiss,
+ onSave,
+}) {
+ return (
+ <Dialog open={isOpened} onClose={onDismiss}>
+ <DialogTitle>{title}</DialogTitle>
+ <DialogContent>
+ <DialogContentText>{contentText}</DialogContentText>
+ {[...inputs]}
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={onDismiss}>Cancelar</Button>
+ <Button onClick={onSave}>Salvar</Button>
+ </DialogActions>
+ </Dialog>
+ );
+}
+
+export default FormDialog;
diff --git a/src/components/PublishAnnouncementCard/index.js b/src/components/PublishAnnouncementCard/index.js
new file mode 100644
index 0000000..67501b2
--- /dev/null
+++ b/src/components/PublishAnnouncementCard/index.js
@@ -0,0 +1,64 @@
+import {
+ Avatar,
+ Button,
+ Card,
+ Stack,
+ TextField,
+ Typography,
+} from '@mui/material';
+import { useState } from 'react';
+import styles from './styles';
+
+function PublishAnnouncementCard({ layoutType, user, value, onChange }) {
+ const [isComposing, setIsComposing] = useState(false);
+ const { card, publishAnnouncement } = styles[layoutType];
+
+ return (
+ <Card sx={card} elevation={4} variant="elevation">
+ {isComposing ? (
+ <Stack
+ sx={publishAnnouncement}
+ alignItems="end"
+ direction="column"
+ spacing={2}
+ >
+ <TextField
+ value={value}
+ onChange={onChange}
+ sx={{ width: '100%' }}
+ id="outlined-multiline-static"
+ label="Escreva um comunicado para sua turma"
+ autoFocus
+ multiline
+ minRows={4}
+ />
+
+ <Stack direction="row" spacing={4}>
+ <Button onClick={() => setIsComposing(false)} variant="text">
+ Cancelar
+ </Button>
+
+ <Button onClick={() => console.log('clicked')} variant="contained">
+ Postar
+ </Button>
+ </Stack>
+ </Stack>
+ ) : (
+ <Stack
+ sx={publishAnnouncement}
+ alignItems="center"
+ direction="row"
+ spacing={2}
+ onClick={() => setIsComposing(true)}
+ >
+ <Avatar alt={user.firstName} src={user.avatar} />
+ <Typography id="outlined-multiline-static" sx={{ width: '100%' }}>
+ Escreva um comunicado para sua turma
+ </Typography>
+ </Stack>
+ )}
+ </Card>
+ );
+}
+
+export default PublishAnnouncementCard;
diff --git a/src/components/PublishAnnouncementCard/styles.js b/src/components/PublishAnnouncementCard/styles.js
new file mode 100644
index 0000000..4bc58fd
--- /dev/null
+++ b/src/components/PublishAnnouncementCard/styles.js
@@ -0,0 +1,47 @@
+// ========== Desktop ==========
+const desktopCard = {
+ width: '100%',
+ padding: '20px',
+};
+
+const desktopPublishAnnouncement = {
+ cursor: 'pointer',
+ ':hover': {
+ color: '#32A041',
+ },
+};
+
+const desktop = {
+ publishAnnouncement: desktopPublishAnnouncement,
+ card: desktopCard,
+};
+
+// ========== Mobile ==========
+const mobilePublishAnnouncement = {
+ cursor: 'pointer',
+ padding: '10px',
+ width: '100%',
+ ':hover': {
+ color: '#32A041',
+ },
+};
+
+const mobileCard = {
+ width: '100%',
+ padding: '10px ',
+};
+
+const mobile = {
+ publishAnnouncement: mobilePublishAnnouncement,
+ card: mobileCard,
+};
+
+// ========== Unset ==========
+
+const unset = {
+ publishAnnouncement: null,
+ card: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/context/auth.js b/src/context/auth.js
index 655b8d1..59ccd82 100644
--- a/src/context/auth.js
+++ b/src/context/auth.js
@@ -1,5 +1,5 @@
import { createContext, useContext, useEffect, useState } from 'react';
-import { getUser, registerUser } from '../services/user-service';
+import { CommonApi } from '../utils/mocks/api';
const AuthContext = createContext();
@@ -22,10 +22,9 @@ function AuthProvider(props) {
const register = data => {
setState({ ...state, status: 'pending' });
- let shouldFail = false;
- return registerUser(data, shouldFail).then(data => {
- if (shouldFail) {
+ return CommonApi.registerUser(data).then(data => {
+ if (data.message) {
return setState({ status: 'error', user: null, error: data });
} else {
return setState({ status: 'success', user: data, error: null });
@@ -35,10 +34,9 @@ function AuthProvider(props) {
const login = (email, password) => {
setState({ ...state, status: 'pending' });
- let shouldFail = email !== 'teste@teste.com' || password !== '#teste1234';
- return getUser(shouldFail).then(data => {
- if (shouldFail) {
+ return CommonApi.getUser(email, password).then(data => {
+ if (data.message) {
return setState({ status: 'error', user: null, error: data });
} else {
return setState({ status: 'success', user: data, error: null });
diff --git a/src/context/user.js b/src/context/user.js
index 23a3df4..4225201 100644
--- a/src/context/user.js
+++ b/src/context/user.js
@@ -1,23 +1,14 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
+import { UserServiceProvider } from '../services/provider';
import { useAuthState } from './auth';
-import {
- getAllAssignments,
- getAssignmentById,
- getAssignmentsByClassId,
- getClassroomAnnouncementsById,
- getClassroomById,
- getClassrooms,
- getFaq,
- getPeopleByClassId,
- getUpcomingAssignmentsByClassId,
-} from '../services/user-service';
const UserContext = createContext();
function UserProvider(props) {
const { user } = useAuthState();
const { pathname } = useLocation();
+ const [userService, setUserService] = useState(null);
const [state, setState] = useState({
user: null,
error: null,
@@ -26,73 +17,23 @@ function UserProvider(props) {
useEffect(() => {
setState({ user, pathname });
- }, [user, pathname]);
-
- const fetchClassrooms = () => getClassrooms(user.id);
-
- const fetchAllAssignments = () => getAllAssignments(user.id);
-
- const fetchAssignmentById = assignmentId => getAssignmentById(assignmentId);
-
- const fetchAssignmentsByClassId = classId => getAssignmentsByClassId(classId);
- const fetchClassroomById = classId => getClassroomById(classId);
-
- const fetchFAQ = () => getFaq();
-
- const fetchClassroomAnnouncements = classId =>
- getClassroomAnnouncementsById(classId);
-
- const fetchUpcomingAssignmentsByClassId = classId =>
- getUpcomingAssignmentsByClassId(classId);
-
- const fetchPeopleByClassId = classId => getPeopleByClassId(classId);
+ async function initUserService() {
+ if (user) {
+ const instance = await UserServiceProvider.getInstance(user);
+ setUserService(instance);
+ }
+ }
+ initUserService();
+ }, [user, pathname]);
- return (
- <UserContext.Provider
- value={{
- state,
- fetchClassrooms,
- fetchAllAssignments,
- fetchAssignmentById,
- fetchAssignmentsByClassId,
- fetchClassroomById,
- fetchFAQ,
- fetchClassroomAnnouncements,
- fetchUpcomingAssignmentsByClassId,
- fetchPeopleByClassId,
- }}
- {...props}
- />
- );
+ return <UserContext.Provider value={{ state, userService }} {...props} />;
}
function useUser() {
- const {
- state,
- fetchClassrooms,
- fetchAssignmentById,
- fetchAllAssignments,
- fetchAssignmentsByClassId,
- fetchClassroomById,
- fetchFAQ,
- fetchClassroomAnnouncements,
- fetchUpcomingAssignmentsByClassId,
- fetchPeopleByClassId,
- } = useContext(UserContext);
+ const { state, userService } = useContext(UserContext);
- return {
- state,
- fetchClassrooms,
- fetchAllAssignments,
- fetchAssignmentById,
- fetchAssignmentsByClassId,
- fetchClassroomById,
- fetchFAQ,
- fetchClassroomAnnouncements,
- fetchUpcomingAssignmentsByClassId,
- fetchPeopleByClassId,
- };
+ return { state, userService };
}
export { UserProvider, useUser };
diff --git a/src/screens/Assignment/index.js b/src/screens/Assignment/index.js
index 3559d3b..3357c2b 100644
--- a/src/screens/Assignment/index.js
+++ b/src/screens/Assignment/index.js
@@ -8,14 +8,14 @@ import View from './View';
function Assignment() {
const params = useParams();
const layoutType = useLayoutType();
- const { fetchAssignmentById } = useUser();
+ const { userService } = useUser();
const [assignment, setAssignment] = useState(null);
const dropzone = useDropzone({ maxFiles: 5 });
useEffect(() => {
async function getAssignmentById(assignmentId) {
document.title = 'Carregando...';
- const result = await fetchAssignmentById(assignmentId);
+ const result = await userService.fetchAssignmentById(assignmentId);
setAssignment(result.data);
}
@@ -27,7 +27,7 @@ function Assignment() {
getAssignmentById(params.id);
updateDocumentTitle();
- }, [params, fetchAssignmentById, assignment]);
+ }, [params, userService, userService.fetchAssignmentById, assignment]);
return (
<View assignment={assignment} dropzone={dropzone} layoutType={layoutType} />
diff --git a/src/screens/Information/index.js b/src/screens/Information/index.js
index b1f7965..1042060 100644
--- a/src/screens/Information/index.js
+++ b/src/screens/Information/index.js
@@ -9,16 +9,16 @@ import { sectors } from './data';
function Information() {
useDocumentTitle('Informações');
const layoutType = useLayoutType();
- const { fetchFAQ } = useUser();
+ const { userService } = useUser();
const [faq, setFaq] = useState(null);
useEffect(() => {
async function getClassrooms() {
- const result = await fetchFAQ();
+ const result = await userService.fetchFAQ();
setFaq(result.data);
}
getClassrooms();
- }, [fetchFAQ]);
+ }, [userService, userService.fetchFAQ]);
return <View faq={faq} sectors={sectors} layoutType={layoutType} />;
}
diff --git a/src/screens/professor/Classroom/AnnouncementsTab/index.js b/src/screens/professor/Classroom/AnnouncementsTab/index.js
new file mode 100644
index 0000000..74fbe3b
--- /dev/null
+++ b/src/screens/professor/Classroom/AnnouncementsTab/index.js
@@ -0,0 +1,525 @@
+import { useState } from 'react';
+import {
+ Button,
+ Card,
+ Container,
+ Grid,
+ IconButton,
+ Link,
+ Menu,
+ MenuItem,
+ Skeleton,
+ Stack,
+ TextField,
+ Tooltip,
+ Typography,
+} from '@mui/material';
+import MoreVertIcon from '@mui/icons-material/MoreVert';
+import AnnouncementCard from '../../../../components/AnnouncementCard';
+import PublishAnnouncementCard from '../../../../components/PublishAnnouncementCard';
+import FormDialog from '../../../../components/FormDialog';
+
+import styles from './styles';
+import jitsiLogo from '../../../../assets/jitsi.svg';
+import { createArrayFrom1ToN } from '../../../../utils/createArrayFrom1ToN';
+
+function AnnouncementsTab({
+ layoutType,
+ announcementsTabData,
+ classroom,
+ onChangeEditInput,
+ onSaveEditChanges,
+ user,
+}) {
+ const [anchorEl, setAnchorEl] = useState({
+ virtualRoom: null,
+ appointmentSlots: null,
+ });
+ const [dialogOpened, setDialogOpened] = useState(null);
+ const [composingTextValue, setComposingTextValue] = useState('');
+ const { container, emptyStateContainer } = styles[layoutType];
+
+ const onSaveEdit = anchorName => {
+ onSaveEditChanges();
+ setDialogOpened(null);
+ setAnchorEl({ ...anchorEl, [anchorName]: null });
+ };
+
+ const onDismissEdit = anchorName => {
+ setDialogOpened(null);
+ setAnchorEl({ ...anchorEl, [anchorName]: null });
+ };
+
+ const layoutResolver = (state, layoutType) => {
+ if (layoutType === 'desktop') {
+ switch (state) {
+ case 'loading':
+ return (
+ <Grid sx={container} container spacing={2}>
+ <Grid sx={{ padding: '0 !important' }} item xs={4}>
+ {createArrayFrom1ToN(3).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="100%"
+ height={200}
+ sx={{ marginBottom: '30px' }}
+ />
+ ))}
+ </Grid>
+ <Grid sx={{ paddingTop: '0 !important' }} item xs={8}>
+ {createArrayFrom1ToN(4).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="100%"
+ height={250}
+ sx={{ marginBottom: '30px' }}
+ />
+ ))}
+ </Grid>
+ </Grid>
+ );
+
+ case 'idle':
+ return (
+ <Grid sx={container} container spacing={2}>
+ <Grid sx={{ padding: '0 !important' }} item xs={4}>
+ <Stack gap="30px">
+ <Card
+ sx={{ width: '100%', padding: '20px', paddingTop: '10px' }}
+ elevation={4}
+ variant="elevation"
+ >
+ <Stack justifyContent="flex-start" spacing={1}>
+ <Container
+ disableGutters
+ sx={{
+ display: 'flex',
+ justifyContent: 'space-between',
+ }}
+ >
+ <Stack direction="row">
+ <img src={jitsiLogo} alt="Jitsi Meet" />
+ <h3 style={{ fontWeight: 500 }}>
+ Sala de aula virtual
+ </h3>
+ </Stack>
+
+ <Tooltip title="Opcoes">
+ <IconButton
+ onClick={e =>
+ setAnchorEl({
+ ...anchorEl,
+ virtualRoom: e.currentTarget,
+ })
+ }
+ aria-label="edit"
+ size="medium"
+ >
+ <MoreVertIcon fontSize="inherit" />
+ </IconButton>
+ </Tooltip>
+ <Menu
+ id="menu-appbar-virtual-room"
+ anchorEl={anchorEl.virtualRoom}
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'right',
+ }}
+ transformOrigin={{
+ vertical: 'top',
+ horizontal: 'right',
+ }}
+ open={Boolean(anchorEl.virtualRoom)}
+ onClose={() =>
+ setAnchorEl({ ...anchorEl, virtualRoom: null })
+ }
+ >
+ <MenuItem
+ onClick={() => setDialogOpened('virtualRoom')}
+ >
+ <Typography textAlign="center">Editar</Typography>
+ </MenuItem>
+ </Menu>
+ <FormDialog
+ isOpened={dialogOpened === 'virtualRoom'}
+ title="Alterar url da sala de aula virtual"
+ contentText="Edite o campo abaixo para alterar a url da sua sala de aula virtual."
+ inputs={[
+ <TextField
+ autoFocus
+ margin="dense"
+ name="virtualRoom"
+ type="text"
+ value={classroom.virtualRoom}
+ onChange={onChangeEditInput}
+ fullWidth
+ variant="standard"
+ />,
+ ]}
+ onDismiss={() => onDismissEdit('virtualRoom')}
+ onSave={() => onSaveEdit('virtualRoom')}
+ />
+ </Container>
+
+ <Button
+ sx={{ marginTop: '15px' }}
+ variant="contained"
+ href={classroom.virtualRoom}
+ target="__blank"
+ >
+ Iniciar aula
+ </Button>
+ </Stack>
+ </Card>
+ <Card
+ sx={{ width: '100%', padding: '20px', paddingTop: '10px' }}
+ elevation={4}
+ variant="elevation"
+ >
+ <Stack justifyContent="flex-start" spacing={1}>
+ <h3 style={{ fontWeight: 500 }}>Próximas Atividades</h3>
+ {announcementsTabData.upcomingAssignments.length !== 0 ? (
+ announcementsTabData.upcomingAssignments.map(ua => (
+ <Link
+ href={`/assignment/${ua.id}`}
+ sx={{ fontSize: '15px' }}
+ key={ua.id}
+ >
+ {ua.title}
+ </Link>
+ ))
+ ) : (
+ <Container disableGutters>
+ <p>Nenhuma atividade encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Card>
+ <Card
+ sx={{ width: '100%', padding: '20px', paddingTop: '10px' }}
+ elevation={4}
+ variant="elevation"
+ >
+ <Stack justifyContent="flex-start" spacing={1}>
+ <Container
+ disableGutters
+ sx={{
+ display: 'flex',
+ justifyContent: 'space-between',
+ }}
+ >
+ <h3 style={{ fontWeight: 500 }}>
+ Horários de Atendimento
+ </h3>
+ <Tooltip title="Opcoes">
+ <IconButton
+ onClick={e =>
+ setAnchorEl({
+ ...anchorEl,
+ appointmentSlots: e.currentTarget,
+ })
+ }
+ aria-label="edit"
+ size="medium"
+ >
+ <MoreVertIcon fontSize="inherit" />
+ </IconButton>
+ </Tooltip>
+ <Menu
+ id="menu-appbar-appointment-slots"
+ anchorEl={anchorEl.appointmentSlots}
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'right',
+ }}
+ transformOrigin={{
+ vertical: 'top',
+ horizontal: 'right',
+ }}
+ open={Boolean(anchorEl.appointmentSlots)}
+ onClose={() =>
+ setAnchorEl({ ...anchorEl, appointmentSlots: null })
+ }
+ >
+ <MenuItem
+ onClick={() => setDialogOpened('appointmentSlots')}
+ >
+ <Typography textAlign="center">Editar</Typography>
+ </MenuItem>
+ </Menu>
+ <FormDialog
+ isOpened={dialogOpened === 'appointmentSlots'}
+ title="Alterar horarios de atendimento"
+ contentText="Edite os campos abaixo para alterar os horarios de atendimento da disciplina."
+ inputs={[
+ classroom.appointmentSlots.map((appts, index) => (
+ <TextField
+ key={index}
+ autoFocus
+ margin="dense"
+ name={index}
+ type="text"
+ value={`${appts.weekDay}, ${appts.start}h - ${appts.end}h`}
+ onChange={onChangeEditInput}
+ fullWidth
+ variant="standard"
+ />
+ )),
+ ]}
+ onDismiss={() => onDismissEdit('appointmentSlots')}
+ onSave={() => onSaveEdit('appointmentSlots')}
+ />
+ </Container>
+ {classroom.appointmentSlots.map((appts, index) => (
+ <Typography key={index} variant="body1">
+ {appts.weekDay}, {appts.start}h - {appts.end}h
+ </Typography>
+ ))}
+ </Stack>
+ </Card>
+ </Stack>
+ </Grid>
+ <Grid sx={{ paddingTop: '0 !important' }} item xs={8}>
+ <Stack
+ sx={{ width: '100%', paddingTop: 0 }}
+ alignItems="center"
+ justifyContent="center"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ >
+ <PublishAnnouncementCard
+ layoutType={layoutType}
+ user={user}
+ value={composingTextValue}
+ onChange={e => setComposingTextValue(e.target.value)}
+ />
+ {announcementsTabData.announcements.length !== 0 ? (
+ announcementsTabData.announcements.map(announcement => (
+ <AnnouncementCard
+ key={announcement.id}
+ announcement={announcement}
+ />
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum comunicado encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Grid>
+ </Grid>
+ );
+
+ case 'gone':
+ return null;
+
+ default:
+ return null;
+ }
+ } else if (layoutType === 'mobile') {
+ switch (state) {
+ case 'loading':
+ return (
+ <Stack
+ alignItems="center"
+ justifyContent="center"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ sx={{ marginTop: '30px' }}
+ >
+ {createArrayFrom1ToN(3).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="100%"
+ height={200}
+ sx={{ marginBottom: '30px' }}
+ />
+ ))}
+ {createArrayFrom1ToN(4).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="100%"
+ height={250}
+ sx={{ marginBottom: '30px' }}
+ />
+ ))}
+ </Stack>
+ );
+
+ case 'idle':
+ return (
+ <Stack
+ alignItems="center"
+ justifyContent="center"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ sx={{ marginTop: '30px' }}
+ >
+ <Stack gap="30px" sx={{ width: '100%' }}>
+ <Card
+ sx={{ width: '100%', padding: '20px', paddingTop: '10px' }}
+ elevation={4}
+ variant="elevation"
+ >
+ <Stack justifyContent="flex-start" spacing={1}>
+ <Container
+ disableGutters
+ sx={{ display: 'flex', justifyContent: 'space-between' }}
+ >
+ <Stack direction="row">
+ <img src={jitsiLogo} alt="Jitsi Meet" />
+ <h3 style={{ fontWeight: 500 }}>
+ Sala de aula virtual
+ </h3>
+ </Stack>
+ <Tooltip title="Opcoes">
+ <IconButton
+ onClick={e => setAnchorEl(e.currentTarget)}
+ aria-label="edit"
+ size="medium"
+ >
+ <MoreVertIcon fontSize="inherit" />
+ </IconButton>
+ </Tooltip>
+ <Menu
+ id="menu-appbar"
+ anchorEl={anchorEl}
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'right',
+ }}
+ transformOrigin={{
+ vertical: 'top',
+ horizontal: 'right',
+ }}
+ open={Boolean(anchorEl)}
+ onClose={() => setAnchorEl(null)}
+ >
+ <MenuItem
+ onClick={() => setDialogOpened('virtualRoom')}
+ >
+ <Typography textAlign="center">Editar</Typography>
+ </MenuItem>
+ </Menu>
+ <FormDialog
+ isOpened={dialogOpened === 'virtualRoom'}
+ title="Alterar url da sala de aula virtual"
+ contentText="Edite o campo abaixo para alterar a url da sua sala de aula virtual."
+ inputs={[
+ <TextField
+ autoFocus
+ margin="dense"
+ name="virtualRoom"
+ type="text"
+ value={classroom.virtualRoom}
+ onChange={onChangeEditInput}
+ fullWidth
+ variant="standard"
+ />,
+ ]}
+ onDismiss={() => onDismissEdit('virtualRoom')}
+ onSave={() => onSaveEdit('virtualRoom')}
+ />
+ </Container>
+ <Button
+ variant="contained"
+ href={classroom.virtualRoom}
+ target="__blank"
+ >
+ Iniciar aula
+ </Button>
+ </Stack>
+ </Card>
+ <Card
+ sx={{ width: '100%', padding: '20px', paddingTop: '10px' }}
+ elevation={4}
+ variant="elevation"
+ >
+ <Stack justifyContent="flex-start" spacing={1}>
+ <h3 style={{ fontWeight: 500 }}>Próximas Atividades</h3>
+
+ {announcementsTabData.upcomingAssignments.length !== 0 ? (
+ announcementsTabData.upcomingAssignments.map(ua => (
+ <Link
+ href={`/assignment/${ua.id}`}
+ sx={{ fontSize: '15px' }}
+ key={ua.id}
+ >
+ {ua.title}
+ </Link>
+ ))
+ ) : (
+ <Container disableGutters>
+ <p>Nenhuma atividade encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Card>
+ <Card
+ sx={{ width: '100%', padding: '20px', paddingTop: '10px' }}
+ elevation={4}
+ variant="elevation"
+ >
+ <Stack justifyContent="flex-start" spacing={1}>
+ <h3 style={{ fontWeight: 500 }}>Horários de Atendimento</h3>
+ {classroom.appointmentSlots.map((appts, index) => (
+ <Typography key={index} variant="body1">
+ {appts.weekDay}, {appts.start}h - {appts.end}h
+ </Typography>
+ ))}
+ </Stack>
+ </Card>
+ </Stack>
+ <Stack
+ sx={{ width: '100%' }}
+ alignItems="center"
+ justifyContent="center"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ >
+ <PublishAnnouncementCard
+ layoutType={layoutType}
+ user={user}
+ value={composingTextValue}
+ onChange={e => setComposingTextValue(e.target.value)}
+ />
+ {announcementsTabData.announcements.length !== 0 ? (
+ announcementsTabData.announcements.map(announcement => (
+ <AnnouncementCard
+ key={announcement.id}
+ announcement={announcement}
+ />
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum comunicado encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Stack>
+ );
+
+ case 'gone':
+ return null;
+
+ default:
+ return null;
+ }
+ }
+ };
+
+ return layoutResolver(
+ announcementsTabData && announcementsTabData.state,
+ layoutType
+ );
+}
+
+export default AnnouncementsTab;
diff --git a/src/screens/professor/Classroom/AnnouncementsTab/styles.js b/src/screens/professor/Classroom/AnnouncementsTab/styles.js
new file mode 100644
index 0000000..46b3813
--- /dev/null
+++ b/src/screens/professor/Classroom/AnnouncementsTab/styles.js
@@ -0,0 +1,49 @@
+// ========== Desktop ==========
+const desktopContainer = {
+ width: '100%',
+ height: '100vh',
+ backgroundColor: '#red',
+ padding: 0,
+ margin: 0,
+ marginTop: '50px',
+};
+
+const desktopEmptyStateContainer = {
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '30px',
+};
+
+const desktop = {
+ container: desktopContainer,
+ emptyStateContainer: desktopEmptyStateContainer,
+};
+
+// ========== Mobile ==========
+const mobileContainer = {
+ width: '90%',
+ backgroundColor: '#red',
+ padding: 0,
+ marginTop: '30px',
+ paddingBottom: '100px',
+};
+
+const mobileEmptyStateContainer = {
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '30px',
+};
+
+const mobile = {
+ container: mobileContainer,
+ emptyStateContainer: mobileEmptyStateContainer,
+};
+
+// ========== Unset ==========
+const unset = {
+ container: null,
+ emptyStateContainer: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/screens/professor/Classroom/AssignmentsTab/index.js b/src/screens/professor/Classroom/AssignmentsTab/index.js
new file mode 100644
index 0000000..2060c40
--- /dev/null
+++ b/src/screens/professor/Classroom/AssignmentsTab/index.js
@@ -0,0 +1,409 @@
+import {
+ Container,
+ Fab,
+ Link,
+ Skeleton,
+ Stack,
+ Typography,
+} from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+import dayjs from 'dayjs';
+import { capitalizeFirstLetter } from '../../../../utils/capitalizeFirstLetter';
+import styles from './styles';
+
+function AssignmentsTab({ assignmentsTabData, layoutType }) {
+ const layoutResolver = (state, assignments, layoutType) => {
+ const {
+ externalContainer,
+ innerContainer,
+ sectionTitle,
+ assignmentContainer,
+ assignmentTypography,
+ assignmentLink,
+ assignmentDueDate,
+ assignmentScores,
+ emptyStateContainer,
+ } = styles[layoutType];
+ if (layoutType === 'desktop') {
+ switch (state) {
+ case 'loading':
+ return (
+ <Container
+ sx={{
+ ...externalContainer,
+ display: 'block',
+ }}
+ disableGutters
+ >
+ <Stack alignItems="center">
+ <Skeleton
+ variant="rectangular"
+ width="90%"
+ height={70}
+ sx={{ marginBottom: '30px' }}
+ />
+ <Stack alignItems="flex-start" sx={{ width: '90%' }}>
+ <Skeleton variant="rectangular" height={50} width="95%" />
+ <Skeleton
+ variant="rectangular"
+ height={20}
+ width={450}
+ sx={{ marginTop: '25px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ height={20}
+ width={300}
+ sx={{ marginTop: '15px' }}
+ />
+ </Stack>
+ <Stack
+ alignItems="flex-start"
+ sx={{ width: '90%', marginTop: '30px' }}
+ >
+ <Skeleton variant="rectangular" height={50} width="95%" />
+ <Skeleton
+ variant="rectangular"
+ height={20}
+ width={450}
+ sx={{ marginTop: '25px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ height={20}
+ width={300}
+ sx={{ marginTop: '15px' }}
+ />
+ </Stack>
+ </Stack>
+
+ <Stack sx={{ marginTop: '50px' }} alignItems="center">
+ <Skeleton
+ variant="rectangular"
+ width="90%"
+ height={70}
+ sx={{ marginBottom: '30px' }}
+ />
+ <Stack alignItems="flex-start" sx={{ width: '90%' }}>
+ <Skeleton variant="rectangular" height={50} width="95%" />
+ <Skeleton
+ variant="rectangular"
+ height={20}
+ width={450}
+ sx={{ marginTop: '25px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ height={20}
+ width={300}
+ sx={{ marginTop: '15px' }}
+ />
+ </Stack>
+ </Stack>
+ </Container>
+ );
+ case 'idle':
+ const assesments = assignments.filter(a => a.type === 'assessment');
+ const projects = assignments.filter(a => a.type === 'project');
+
+ return (
+ <Container sx={externalContainer} disableGutters>
+ <Fab
+ sx={{ width: 'fit-content', marginRight: '5%' }}
+ color="primary"
+ aria-label="add"
+ variant="extended"
+ >
+ <AddIcon />
+ Criar atividade
+ </Fab>
+ <Container sx={innerContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h4">
+ Provas
+ </Typography>
+ <Stack alignItems="center">
+ {assesments.length !== 0 ? (
+ assesments.map(a => (
+ <Container
+ key={a.id}
+ sx={assignmentContainer}
+ disableGutters
+ >
+ <Typography variant="body1" sx={assignmentTypography}>
+ <Link
+ sx={assignmentLink}
+ href={`/assignment/${a.id}`}
+ >
+ {a.title}
+ </Link>
+ </Typography>
+ <Typography
+ sx={assignmentDueDate}
+ variant="p"
+ component="div"
+ >
+ <strong>Data de entrega: </strong>{' '}
+ {capitalizeFirstLetter(
+ dayjs(a.dueDate).format('dddd, DD/MM | HH:mm[h]')
+ )}
+ </Typography>
+ <Typography
+ sx={assignmentScores}
+ variant="p"
+ component="div"
+ >
+ <strong>Valor: </strong>
+ {a.scores.map(s => s.value).join(', ')} pts
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhuma prova encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+
+ <Container sx={innerContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h4">
+ Trabalhos
+ </Typography>
+ <Stack alignItems="center">
+ {projects.length !== 0 ? (
+ projects.map(a => (
+ <Container
+ key={a.id}
+ sx={assignmentContainer}
+ disableGutters
+ >
+ <Typography variant="body1" sx={assignmentTypography}>
+ <Link
+ sx={assignmentLink}
+ href={`/assignment/${a.id}`}
+ >
+ {a.title}
+ </Link>
+ </Typography>
+ <Typography
+ sx={assignmentDueDate}
+ variant="p"
+ component="div"
+ >
+ <strong>Data de entrega: </strong>{' '}
+ {capitalizeFirstLetter(
+ dayjs(a.dueDate).format('dddd, DD/MM | HH:mm[h]')
+ )}
+ </Typography>
+ <Typography
+ sx={assignmentScores}
+ variant="p"
+ component="div"
+ >
+ <strong>Valor: </strong>
+ {a.scores.map(s => s.value).join(', ')} pts
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum trabalho encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+ </Container>
+ );
+ case 'gone':
+ return null;
+ default:
+ return null;
+ }
+ } else if (layoutType === 'mobile') {
+ switch (state) {
+ case 'loading':
+ return (
+ <Stack
+ alignItems="center"
+ flexWrap="wrap"
+ direction="row"
+ sx={{ marginTop: '30px' }}
+ >
+ <Skeleton
+ variant="rectangular"
+ width="100%"
+ height={70}
+ sx={{ marginTop: '30px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ width="100%"
+ height={30}
+ sx={{ marginTop: '20px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ width="100%"
+ height={15}
+ sx={{ marginTop: '20px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ width={250}
+ height={15}
+ sx={{ marginTop: '10px' }}
+ />
+
+ <Skeleton
+ variant="rectangular"
+ width="100%"
+ height={70}
+ sx={{ marginTop: '50px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ width="100%"
+ height={30}
+ sx={{ marginTop: '20px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ width="100%"
+ height={15}
+ sx={{ marginTop: '20px' }}
+ />
+ <Skeleton
+ variant="rectangular"
+ width={250}
+ height={15}
+ sx={{ marginTop: '10px' }}
+ />
+ </Stack>
+ );
+ case 'idle':
+ const assesments = assignments.filter(a => a.type === 'assessment');
+ const projects = assignments.filter(a => a.type === 'project');
+
+ return (
+ <Container sx={externalContainer} disableGutters>
+ <Fab
+ sx={{ width: '100%' }}
+ color="primary"
+ aria-label="add"
+ variant="extended"
+ >
+ <AddIcon />
+ Criar atividade
+ </Fab>
+ <Container sx={innerContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h4">
+ Provas
+ </Typography>
+ <Stack alignItems="center">
+ {assesments.length !== 0 ? (
+ assesments.map(a => (
+ <Container
+ key={a.id}
+ sx={assignmentContainer}
+ disableGutters
+ >
+ <Typography variant="body1" sx={assignmentTypography}>
+ <Link
+ sx={assignmentLink}
+ href={`/assignment/${a.id}`}
+ >
+ {a.title}
+ </Link>
+ </Typography>
+ <Typography
+ sx={assignmentDueDate}
+ variant="p"
+ component="div"
+ >
+ <strong>Data de entrega: </strong>{' '}
+ {capitalizeFirstLetter(
+ dayjs(a.dueDate).format('dddd, DD/MM | HH:mm[h]')
+ )}
+ </Typography>
+ <Typography
+ sx={assignmentScores}
+ variant="p"
+ component="div"
+ >
+ <strong>Valor: </strong>
+ {a.scores.map(s => s.value).join(', ')} pts
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhuma prova encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+
+ <Container sx={innerContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h4">
+ Trabalhos
+ </Typography>
+ <Stack alignItems="center">
+ {projects.length !== 0 ? (
+ projects.map(a => (
+ <Container
+ key={a.id}
+ sx={assignmentContainer}
+ disableGutters
+ >
+ <Typography variant="body1" sx={assignmentTypography}>
+ <Link
+ sx={assignmentLink}
+ href={`/assignment/${a.id}`}
+ >
+ {a.title}
+ </Link>
+ </Typography>
+ <Typography
+ sx={assignmentDueDate}
+ variant="p"
+ component="div"
+ >
+ <strong>Data de entrega: </strong>{' '}
+ {capitalizeFirstLetter(
+ dayjs(a.dueDate).format('dddd, DD/MM | HH:mm[h]')
+ )}
+ </Typography>
+ <Typography
+ sx={assignmentScores}
+ variant="p"
+ component="div"
+ >
+ <strong>Valor: </strong>
+ {a.scores.map(s => s.value).join(', ')} pts
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum trabalho encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+ </Container>
+ );
+ case 'gone':
+ return null;
+ default:
+ return null;
+ }
+ }
+ };
+ return layoutResolver(
+ assignmentsTabData && assignmentsTabData.state,
+ assignmentsTabData && assignmentsTabData.assignments,
+ layoutType
+ );
+}
+
+export default AssignmentsTab;
diff --git a/src/screens/professor/Classroom/AssignmentsTab/styles.js b/src/screens/professor/Classroom/AssignmentsTab/styles.js
new file mode 100644
index 0000000..0493789
--- /dev/null
+++ b/src/screens/professor/Classroom/AssignmentsTab/styles.js
@@ -0,0 +1,138 @@
+// ========== Desktop ==========
+const desktopExternalContainer = {
+ display: 'flex',
+ flexDirection: 'column',
+ marginTop: '50px',
+ height: '100vh',
+ alignItems: 'flex-end',
+};
+
+const desktopInnerContainer = {
+ width: '90%',
+ marginBottom: '30px',
+};
+
+const desktopSectionTitle = {
+ padding: '10px',
+ borderBottom: '2px solid #00420D',
+ color: '#00420D',
+};
+
+const desktopAssignmentContainer = {
+ width: '95%',
+ padding: '20px',
+ borderBottom: '2px solid #BCBCBC',
+};
+
+const desktopAssignmentTypography = {};
+
+const desktopAssignmentLink = {
+ color: 'black',
+ textDecoration: 'underline #000000',
+};
+
+const desktopAssignmentDueDate = {
+ marginTop: '15px',
+ fontSize: '15px',
+};
+
+const desktopAssignmentScores = {
+ fontSize: '15px',
+};
+
+const desktopEmptyStateContainer = {
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '30px',
+};
+
+const desktop = {
+ externalContainer: desktopExternalContainer,
+ innerContainer: desktopInnerContainer,
+ sectionTitle: desktopSectionTitle,
+ assignmentContainer: desktopAssignmentContainer,
+ assignmentTypography: desktopAssignmentTypography,
+ assignmentLink: desktopAssignmentLink,
+ assignmentDueDate: desktopAssignmentDueDate,
+ assignmentScores: desktopAssignmentScores,
+ emptyStateContainer: desktopEmptyStateContainer,
+};
+
+// ========== Mobile ==========
+const mobileExternalContainer = {
+ marginTop: '50px',
+ height: '100vh',
+};
+
+const mobileInnerContainer = {
+ width: '100%',
+ marginBottom: '30px',
+ marginTop: '30px',
+};
+
+const mobileSectionTitle = {
+ padding: '10px',
+ borderBottom: '2px solid #00420D',
+ color: '#00420D',
+};
+
+const mobileAssignmentContainer = {
+ width: '100%',
+ padding: '20px',
+ borderBottom: '2px solid #BCBCBC',
+};
+
+const mobileAssignmentTypography = {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ display: '-webkit-box',
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: 'vertical',
+};
+
+const mobileAssignmentLink = {
+ color: 'black',
+ textDecoration: 'underline #000000',
+};
+
+const mobileAssignmentDueDate = {
+ marginTop: '10px',
+ fontSize: '12px',
+};
+
+const mobileAssignmentScores = {
+ fontSize: '12px',
+};
+
+const mobileEmptyStateContainer = {
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '30px',
+};
+
+const mobile = {
+ externalContainer: mobileExternalContainer,
+ innerContainer: mobileInnerContainer,
+ sectionTitle: mobileSectionTitle,
+ assignmentContainer: mobileAssignmentContainer,
+ assignmentTypography: mobileAssignmentTypography,
+ assignmentLink: mobileAssignmentLink,
+ assignmentDueDate: mobileAssignmentDueDate,
+ assignmentScores: mobileAssignmentScores,
+ emptyStateContainer: mobileEmptyStateContainer,
+};
+
+// ========== Unset ==========
+const unset = {
+ externalContainer: null,
+ innerContainer: null,
+ sectionTitle: null,
+ assignmentContainer: null,
+ assignmentTypography: null,
+ assignmentLink: null,
+ assignmentDueDate: null,
+ assignmentScores: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/screens/professor/Classroom/GradesTab/index.js b/src/screens/professor/Classroom/GradesTab/index.js
new file mode 100644
index 0000000..785bd50
--- /dev/null
+++ b/src/screens/professor/Classroom/GradesTab/index.js
@@ -0,0 +1,34 @@
+function GradesTab({ gradesTabData, layoutType }) {
+ const layoutResolver = (state, grades, layoutType) => {
+ if (layoutType === 'desktop') {
+ switch (state) {
+ case 'loading':
+ return <h1>Loading...</h1>;
+ case 'idle':
+ return <h1>Grades Tab</h1>;
+ case 'gone':
+ return null;
+ default:
+ return null;
+ }
+ } else if (layoutType === 'mobile') {
+ switch (state) {
+ case 'loading':
+ return <h1>Loading...</h1>;
+ case 'idle':
+ return <h1>Grades Tab</h1>;
+ case 'gone':
+ return null;
+ default:
+ return null;
+ }
+ }
+ };
+ return layoutResolver(
+ gradesTabData && gradesTabData.state,
+ gradesTabData && gradesTabData.grades,
+ layoutType
+ );
+}
+
+export default GradesTab;
diff --git a/src/screens/professor/Classroom/GradesTab/styles.js b/src/screens/professor/Classroom/GradesTab/styles.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/screens/professor/Classroom/GradesTab/styles.js
diff --git a/src/screens/professor/Classroom/Header/index.js b/src/screens/professor/Classroom/Header/index.js
new file mode 100644
index 0000000..e077313
--- /dev/null
+++ b/src/screens/professor/Classroom/Header/index.js
@@ -0,0 +1,62 @@
+import {
+ Avatar,
+ AvatarGroup,
+ Container,
+ Paper,
+ Skeleton,
+ Stack,
+ Tab,
+ Tabs,
+ Tooltip,
+ Typography,
+} from '@mui/material';
+import { TAB_OPTIONS } from '../tabOptions';
+import styles from './styles';
+
+function Header({
+ layoutType,
+ classroom,
+ selectedTabOption,
+ onSelectTabOption,
+ isLoading,
+}) {
+ const { title, paper, tabs, avatar, tooltip } = styles[layoutType];
+ return classroom === null ? (
+ <Skeleton variant="rectangular" width="100%" height={240} />
+ ) : (
+ <Container disableGutters>
+ <Paper sx={paper(classroom.color)} elevation={4} variant="elevation">
+ <h1 style={title}>{classroom.name}</h1>
+ <Stack alignItems="center" direction="row" spacing={1}>
+ <AvatarGroup total={classroom.teachers.length}>
+ {classroom.teachers.map(t => (
+ <Avatar key={t.name} alt={t.name} src={t.avatar} sx={avatar} />
+ ))}
+ </AvatarGroup>
+ <Tooltip title={classroom.teachers.map(t => t.name).join(', ')}>
+ <Typography sx={tooltip} variant="body3" color="text.secondary">
+ {classroom.teachers.map(t => t.name).join(', ')}
+ </Typography>
+ </Tooltip>
+ </Stack>
+ <Tabs
+ value={selectedTabOption}
+ onChange={onSelectTabOption}
+ aria-label="Tabs para informações da disciplina"
+ variant={layoutType === 'mobile' ? 'scrollable' : 'fullWidth'}
+ sx={tabs}
+ >
+ {Object.values(TAB_OPTIONS).map(option => (
+ <Tab
+ key={option.value}
+ label={option.lable}
+ disabled={isLoading && option.value !== selectedTabOption}
+ />
+ ))}
+ </Tabs>
+ </Paper>
+ </Container>
+ );
+}
+
+export default Header;
diff --git a/src/screens/professor/Classroom/Header/styles.js b/src/screens/professor/Classroom/Header/styles.js
new file mode 100644
index 0000000..03ba4ab
--- /dev/null
+++ b/src/screens/professor/Classroom/Header/styles.js
@@ -0,0 +1,90 @@
+// ========== Desktop ==========
+const desktopTitle = {
+ fontWeight: 500,
+};
+
+const desktopPaper = classColor => ({
+ width: '100%',
+ borderTop: `5px solid ${classColor}`,
+ padding: '30px',
+});
+
+const desktopTabs = {
+ marginLeft: '-20px',
+ marginRight: '-20px',
+ marginBottom: '-30px',
+ marginTop: '30px',
+};
+
+const desktopAvatar = {
+ width: 30,
+ height: 30,
+};
+
+const desktopTooltip = {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ display: '-webkit-box',
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: 'vertical',
+};
+
+const desktop = {
+ title: desktopTitle,
+ paper: desktopPaper,
+ tabs: desktopTabs,
+ avatar: desktopAvatar,
+ tooltip: desktopTooltip,
+};
+
+// ========== Mobile ==========
+const mobileTitle = {
+ fontWeight: 500,
+ fontSize: '25px',
+};
+
+const mobilePaper = classColor => ({
+ width: '100%',
+ borderTop: `5px solid ${classColor}`,
+ padding: '20px',
+});
+
+const mobileTabs = {
+ marginLeft: '-10px',
+ marginRight: '-10px',
+ marginBottom: '-20px',
+ marginTop: '30px',
+};
+
+const mobileAvatar = {
+ width: 30,
+ height: 30,
+};
+
+const mobileTooltip = {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ display: '-webkit-box',
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: 'vertical',
+};
+
+const mobile = {
+ title: mobileTitle,
+ paper: mobilePaper,
+ tabs: mobileTabs,
+ avatar: mobileAvatar,
+ tooltip: mobileTooltip,
+};
+
+// ========== Unset ==========
+const unset = {
+ title: null,
+ paper: null,
+ tabs: null,
+ avatar: null,
+ tooltip: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/screens/Classroom/PeopleTab/index.js b/src/screens/professor/Classroom/PeopleTab/index.js
index 3080ee3..9dfde7b 100644
--- a/src/screens/Classroom/PeopleTab/index.js
+++ b/src/screens/professor/Classroom/PeopleTab/index.js
@@ -1,5 +1,5 @@
import { Avatar, Container, Skeleton, Stack, Typography } from '@mui/material';
-import { createArrayFrom1ToN } from '../../../utils/createArrayFrom1ToN';
+import { createArrayFrom1ToN } from '../../../../utils/createArrayFrom1ToN';
import styles from './styles';
function PeopleTab({ layoutType, peopleTabData }) {
diff --git a/src/screens/Classroom/PeopleTab/styles.js b/src/screens/professor/Classroom/PeopleTab/styles.js
index 30668db..30668db 100644
--- a/src/screens/Classroom/PeopleTab/styles.js
+++ b/src/screens/professor/Classroom/PeopleTab/styles.js
diff --git a/src/screens/professor/Classroom/View.js b/src/screens/professor/Classroom/View.js
new file mode 100644
index 0000000..59dbd92
--- /dev/null
+++ b/src/screens/professor/Classroom/View.js
@@ -0,0 +1,51 @@
+import { Container } from '@mui/system';
+import AnnouncementsTab from './AnnouncementsTab';
+import AssignmentsTab from './AssignmentsTab';
+import GradesTab from './GradesTab';
+import Header from './Header';
+import PeopleTab from './PeopleTab';
+import styles from './styles';
+
+function View({
+ layoutType,
+ classroom,
+ selectedTabOption,
+ onSelectTabOption,
+ announcementsTabData,
+ assignmentsTabData,
+ peopleTabData,
+ gradesTabData,
+ user,
+ onChangeEditInput,
+ onSaveEditChanges,
+ isLoading,
+}) {
+ const { container } = styles[layoutType];
+ return (
+ <Container disableGutters sx={container}>
+ <Header
+ layoutType={layoutType}
+ classroom={classroom && classroom}
+ selectedTabOption={selectedTabOption}
+ onSelectTabOption={onSelectTabOption}
+ isLoading={isLoading}
+ />
+ <AnnouncementsTab
+ layoutType={layoutType}
+ announcementsTabData={announcementsTabData}
+ classroom={classroom && classroom}
+ user={user && user}
+ onChangeEditInput={onChangeEditInput}
+ onSaveEditChanges={onSaveEditChanges}
+ />
+ <AssignmentsTab
+ layoutType={layoutType}
+ assignmentsTabData={assignmentsTabData}
+ />
+ <PeopleTab layoutType={layoutType} peopleTabData={peopleTabData} />
+ <GradesTab layoutType={layoutType} gradesTabData={gradesTabData} />
+ </Container>
+ );
+}
+
+export default View;
diff --git a/src/screens/professor/Classroom/index.js b/src/screens/professor/Classroom/index.js
new file mode 100644
index 0000000..fa86816
--- /dev/null
+++ b/src/screens/professor/Classroom/index.js
@@ -0,0 +1,156 @@
+import { useCallback, useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import { useUser } from '../../../context/user';
+import useLayoutType from '../../../hooks/useLayoutType';
+import { TAB_OPTIONS } from './tabOptions';
+import View from './View';
+
+function Classroom() {
+ const params = useParams();
+ const layoutType = useLayoutType();
+ const { userService, state } = useUser();
+ const [classroom, setClassroom] = useState(null);
+ const [tabData, setTabData] = useState(null);
+
+ const [selectedTabOption, setSelectedTabOption] = useState(
+ TAB_OPTIONS.announcements.value
+ );
+
+ const onChangeEditInput = e => {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setClassroom(prev => ({ ...prev, [name]: value }));
+ };
+
+ const onSaveEditChanges = () => {
+ console.log('Saving edit changes...');
+ console.log(classroom);
+ };
+
+ const fetchAndPopulateAnnouncementsTabData = useCallback(async () => {
+ setTabData({ tab: 'announcements', state: 'loading' });
+ const announcements = await userService.fetchClassroomAnnouncements(
+ params.id
+ );
+
+ const upcomingAssignments =
+ await userService.fetchUpcomingAssignmentsByClassId(params.id);
+
+ setTabData({
+ tab: 'announcements',
+ state: 'idle',
+ announcements: [...announcements.data],
+ upcomingAssignments: [...upcomingAssignments.data],
+ });
+ }, [userService, params.id]);
+
+ const fetchAndPopulateAssignmentsTabData = useCallback(async () => {
+ setTabData({ tab: 'assignments', state: 'loading' });
+ const assignments = await userService.fetchAssignmentsByClassId(params.id);
+
+ setTabData({
+ tab: 'assignments',
+ state: 'idle',
+ assignments: [...assignments.data],
+ });
+ }, [userService, params.id]);
+
+ useEffect(() => {
+ async function getClassroomById(classId) {
+ document.title = 'Carregando...';
+ const result = await userService.fetchClassroomById(classId);
+ setClassroom(result.data);
+ }
+
+ function updateDocumentTitle() {
+ if (classroom !== null) {
+ document.title = classroom.name;
+ }
+ }
+
+ if (!classroom) {
+ getClassroomById(params.id);
+ }
+
+ updateDocumentTitle();
+ }, [userService, userService.fetchClassroomById, params, classroom]);
+
+ const fetchAndPopulatePeopleTabData = useCallback(async () => {
+ setTabData({ tab: 'people', state: 'loading' });
+ const people = await userService.fetchPeopleByClassId(params.id);
+
+ setTabData({
+ tab: 'people',
+ state: 'idle',
+ people: [...people.data],
+ });
+ }, [userService, params.id]);
+
+ const fetchAndPopulateGradesTabData = useCallback(async () => {
+ setTabData({ tab: 'people', state: 'loading' });
+ const grades = await userService.fetchPeopleByClassId(params.id);
+
+ setTabData({
+ tab: 'grades',
+ state: 'idle',
+ grades: [...grades.data],
+ });
+ }, [userService, params.id]);
+
+ useEffect(() => {
+ async function getSelectedTabData() {
+ switch (selectedTabOption) {
+ case TAB_OPTIONS.announcements.value:
+ fetchAndPopulateAnnouncementsTabData();
+ break;
+ case TAB_OPTIONS.assignments.value:
+ fetchAndPopulateAssignmentsTabData();
+ break;
+ case TAB_OPTIONS.people.value:
+ fetchAndPopulatePeopleTabData();
+ break;
+ case TAB_OPTIONS.grades.value:
+ fetchAndPopulateGradesTabData();
+ break;
+ default:
+ console.log('Invalid tab option');
+ }
+ }
+ getSelectedTabData();
+ }, [
+ selectedTabOption,
+ params,
+ fetchAndPopulateAnnouncementsTabData,
+ fetchAndPopulateAssignmentsTabData,
+ fetchAndPopulatePeopleTabData,
+ fetchAndPopulateGradesTabData,
+ ]);
+
+ return (
+ <View
+ layoutType={layoutType}
+ classroom={classroom}
+ selectedTabOption={selectedTabOption}
+ onSelectTabOption={(_, value) => setSelectedTabOption(value)}
+ announcementsTabData={
+ tabData && tabData.tab === 'announcements' ? tabData : { state: 'gone' }
+ }
+ assignmentsTabData={
+ tabData && tabData.tab === 'assignments' ? tabData : { state: 'gone' }
+ }
+ peopleTabData={
+ tabData && tabData.tab === 'people' ? tabData : { state: 'gone' }
+ }
+ gradesTabData={
+ tabData && tabData.tab === 'grades' ? tabData : { state: 'gone' }
+ }
+ user={state && state.user}
+ onChangeEditInput={onChangeEditInput}
+ onSaveEditChanges={onSaveEditChanges}
+ isLoading={tabData && tabData.state === 'loading'}
+ />
+ );
+}
+
+export default Classroom;
diff --git a/src/screens/Classroom/styles.js b/src/screens/professor/Classroom/styles.js
index fe6018c..fe6018c 100644
--- a/src/screens/Classroom/styles.js
+++ b/src/screens/professor/Classroom/styles.js
diff --git a/src/screens/professor/Classroom/tabOptions.js b/src/screens/professor/Classroom/tabOptions.js
new file mode 100644
index 0000000..fdc3e98
--- /dev/null
+++ b/src/screens/professor/Classroom/tabOptions.js
@@ -0,0 +1,20 @@
+const TAB_OPTIONS = {
+ announcements: {
+ value: 0,
+ lable: 'Comunicados',
+ },
+ assignments: {
+ value: 1,
+ lable: 'Atividades',
+ },
+ people: {
+ value: 2,
+ lable: 'Pessoas',
+ },
+ grades: {
+ value: 3,
+ lable: 'Notas',
+ },
+};
+
+export { TAB_OPTIONS };
diff --git a/src/screens/professor/Home/View.js b/src/screens/professor/Home/View.js
new file mode 100644
index 0000000..0651aea
--- /dev/null
+++ b/src/screens/professor/Home/View.js
@@ -0,0 +1,200 @@
+import { Grid, Skeleton, Stack } from '@mui/material';
+import { Container } from '@mui/system';
+import AssignmentCard from '../../../components/AssignmentCard';
+import ClassCard from '../../../components/ClassCard';
+import { createArrayFrom1ToN } from '../../../utils/createArrayFrom1ToN';
+import styles from './styles';
+
+function View({
+ layoutType,
+ classrooms,
+ assignmentsToReview,
+ onClickClassCard,
+}) {
+ const { container, divider, assignmentsStack, onClickAssignmentCard } =
+ styles[layoutType];
+
+ if (layoutType === 'desktop') {
+ return (
+ <Grid sx={container} container spacing={2}>
+ <Grid item xs={8}>
+ <h1>Minhas Turmas</h1>
+ <Stack alignItems="center" flexWrap="wrap" direction="row" gap="30px">
+ {classrooms === null ? (
+ createArrayFrom1ToN(6).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width={390}
+ height={145}
+ />
+ ))
+ ) : classrooms.length !== 0 ? (
+ classrooms.map(classroom => (
+ <ClassCard
+ key={classroom.name}
+ abbreviation={classroom.abbreviation}
+ title={classroom.name}
+ color={classroom.color}
+ teachers={classroom.teachers}
+ course={classroom.course}
+ layoutType={layoutType}
+ onClick={() => onClickClassCard(classroom.id)}
+ />
+ ))
+ ) : (
+ <Container
+ sx={{
+ height: '100vh',
+ display: 'flex',
+ justifyContent: 'center',
+ }}
+ disableGutters
+ >
+ <p>Nenhuma sala de aula encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Grid>
+ <Grid sx={divider} item xs={4}>
+ <h1>Atividades para corrigir</h1>
+ <Stack
+ sx={assignmentsStack}
+ alignItems="end"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ >
+ {assignmentsToReview === null ? (
+ createArrayFrom1ToN(6).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="35em"
+ height={145}
+ />
+ ))
+ ) : assignmentsToReview.length !== 0 ? (
+ assignmentsToReview.map(assignment => (
+ <AssignmentCard
+ key={assignment.title}
+ title={assignment.title}
+ classrooms={assignment.classrooms}
+ dueDate={assignment.dueDate}
+ scores={assignment.scores}
+ layoutType={layoutType}
+ deliveredByStudents={assignment.deliveredByStudents}
+ reviewed={assignment.reviewed}
+ isAssignmentToReview={assignment.status !== null}
+ total={assignment.total}
+ onClick={() => onClickAssignmentCard(assignment.id)}
+ />
+ ))
+ ) : (
+ <Container
+ sx={{
+ height: '100vh',
+ display: 'flex',
+ justifyContent: 'center',
+ }}
+ disableGutters
+ >
+ <p>Nenhuma atividade encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Grid>
+ </Grid>
+ );
+ } else if (layoutType === 'mobile') {
+ return (
+ <Stack sx={container}>
+ <h1>Minhas Turmas</h1>
+ <Stack
+ alignItems="center"
+ justifyContent="center"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ >
+ {classrooms === null ? (
+ createArrayFrom1ToN(6).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="100%"
+ height={245}
+ />
+ ))
+ ) : classrooms.length !== 0 ? (
+ classrooms.map(classroom => (
+ <ClassCard
+ key={classroom.name}
+ abbreviation={classroom.abbreviation}
+ title={classroom.name}
+ color={classroom.color}
+ teachers={classroom.teachers}
+ course={classroom.course}
+ layoutType={layoutType}
+ onClick={() => onClickClassCard(classroom.id)}
+ />
+ ))
+ ) : (
+ <Container disableGutters>
+ <p>Nenhuma sala de aula encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ <h1 style={divider}>Atividades para corrigir</h1>
+ <Stack
+ sx={assignmentsStack}
+ alignItems="center"
+ justifyContent="center"
+ flexWrap="wrap"
+ direction="row"
+ gap="30px"
+ >
+ {assignmentsToReview === null ? (
+ createArrayFrom1ToN(6).map(i => (
+ <Skeleton
+ key={i}
+ variant="rectangular"
+ width="35em"
+ height={145}
+ />
+ ))
+ ) : assignmentsToReview.length !== 0 ? (
+ assignmentsToReview.map(assignment => (
+ <AssignmentCard
+ key={assignment.title}
+ title={assignment.title}
+ classrooms={assignment.classrooms}
+ dueDate={assignment.dueDate}
+ scores={assignment.scores}
+ layoutType={layoutType}
+ deliveredByStudents={assignment.deliveredByStudents}
+ reviewed={assignment.reviewed}
+ isAssignmentToReview={assignment.status !== null}
+ total={assignment.total}
+ onClick={() => onClickAssignmentCard(assignment.id)}
+ />
+ ))
+ ) : (
+ <Container
+ sx={{
+ height: '100vh',
+ display: 'flex',
+ justifyContent: 'center',
+ }}
+ disableGutters
+ >
+ <p>Nenhuma atividade encontrada!</p>
+ </Container>
+ )}
+ </Stack>
+ </Stack>
+ );
+ }
+}
+
+export default View;
diff --git a/src/screens/professor/Home/index.js b/src/screens/professor/Home/index.js
new file mode 100644
index 0000000..d16c20d
--- /dev/null
+++ b/src/screens/professor/Home/index.js
@@ -0,0 +1,51 @@
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useUser } from '../../../context/user';
+import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
+import useLayoutType from '../../../hooks/useLayoutType';
+import View from './View';
+
+function Home() {
+ useDocumentTitle('Página Inicial');
+ const navigate = useNavigate();
+ const layoutType = useLayoutType();
+ const { userService } = useUser();
+ const [classrooms, setClassrooms] = useState(null);
+ const [assignmentsToReview, setAssignmentsToReview] = useState(null);
+
+ useEffect(() => {
+ async function getClassrooms() {
+ const result = await userService.fetchClassrooms();
+ setClassrooms(result.data);
+ }
+ getClassrooms();
+ }, [userService, userService.fetchClassrooms]);
+
+ useEffect(() => {
+ async function getAssignmentsToReview() {
+ const result = await userService.fetchAssignmentsToReview();
+ setAssignmentsToReview(result.data);
+ }
+ getAssignmentsToReview();
+ }, [userService, userService.fetchAllAssignments]);
+
+ const onClickClassCard = id => {
+ navigate(`/class/${id}`);
+ };
+
+ const onClickAssignmentCard = id => {
+ navigate(`/assignment/${id}`);
+ };
+
+ return (
+ <View
+ layoutType={layoutType}
+ classrooms={classrooms}
+ assignmentsToReview={assignmentsToReview}
+ onClickClassCard={onClickClassCard}
+ onClickAssignmentCard={onClickAssignmentCard}
+ />
+ );
+}
+
+export default Home;
diff --git a/src/screens/professor/Home/styles.js b/src/screens/professor/Home/styles.js
new file mode 100644
index 0000000..cd02a41
--- /dev/null
+++ b/src/screens/professor/Home/styles.js
@@ -0,0 +1,42 @@
+// ========== Desktop ==========
+const desktopContainer = {
+ height: '100vh',
+ margin: 0,
+};
+
+const desktopDivider = {
+ borderLeft: '4px solid #CFCFCF',
+};
+
+const desktop = {
+ container: desktopContainer,
+ divider: desktopDivider,
+};
+
+// ========== Mobile ==========
+const mobileContainer = {
+ height: 'inherit',
+ width: '100%',
+ padding: '10px 20px ',
+ margin: 0,
+};
+
+const mobileDivider = {
+ borderTop: '2px solid #CFCFCF',
+ paddingTop: '15px',
+};
+
+const mobile = {
+ container: mobileContainer,
+ divider: mobileDivider,
+};
+
+// ========== Unset ==========
+const unset = {
+ container: null,
+ divider: null,
+ assignmentsStack: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/screens/Classroom/AnnouncementsTab/index.js b/src/screens/student/Classroom/AnnouncementsTab/index.js
index 5540a94..cded9cc 100644
--- a/src/screens/Classroom/AnnouncementsTab/index.js
+++ b/src/screens/student/Classroom/AnnouncementsTab/index.js
@@ -8,11 +8,11 @@ import {
Stack,
Typography,
} from '@mui/material';
-import AnnouncementCard from '../../../components/AnnouncementCard';
-import { createArrayFrom1ToN } from '../../../utils/createArrayFrom1ToN';
+import AnnouncementCard from '../../../../components/AnnouncementCard';
+import { createArrayFrom1ToN } from '../../../../utils/createArrayFrom1ToN';
import styles from './styles';
-import jitsiLogo from '../../../assets/jitsi.svg';
+import jitsiLogo from '../../../../assets/jitsi.svg';
function AnnouncementsTab({ layoutType, announcementsTabData, classroom }) {
const { container, emptyStateContainer } = styles[layoutType];
@@ -64,7 +64,9 @@ function AnnouncementsTab({ layoutType, announcementsTabData, classroom }) {
sx={{ display: 'flex', justifyContent: 'row' }}
>
<img src={jitsiLogo} alt="Jitsi Meet" />
- <h3 style={{ fontWeight: 500 }}>Jitsi</h3>
+ <h3 style={{ fontWeight: 500 }}>
+ Sala de aula virtual
+ </h3>
</Container>
<Button
@@ -205,7 +207,7 @@ function AnnouncementsTab({ layoutType, announcementsTabData, classroom }) {
sx={{ display: 'flex', justifyContent: 'row' }}
>
<img src={jitsiLogo} alt="Jitsi Meet" />
- <h3 style={{ fontWeight: 500 }}>Jitsi</h3>
+ <h3 style={{ fontWeight: 500 }}>Sala de aula virtual</h3>
</Container>
<Button
diff --git a/src/screens/Classroom/AnnouncementsTab/styles.js b/src/screens/student/Classroom/AnnouncementsTab/styles.js
index d7d218a..d7d218a 100644
--- a/src/screens/Classroom/AnnouncementsTab/styles.js
+++ b/src/screens/student/Classroom/AnnouncementsTab/styles.js
diff --git a/src/screens/Classroom/AssignmentsTab/index.js b/src/screens/student/Classroom/AssignmentsTab/index.js
index bc089fe..19757b4 100644
--- a/src/screens/Classroom/AssignmentsTab/index.js
+++ b/src/screens/student/Classroom/AssignmentsTab/index.js
@@ -1,6 +1,6 @@
import { Container, Link, Skeleton, Stack, Typography } from '@mui/material';
import dayjs from 'dayjs';
-import { capitalizeFirstLetter } from '../../../utils/capitalizeFirstLetter';
+import { capitalizeFirstLetter } from '../../../../utils/capitalizeFirstLetter';
import styles from './styles';
function AssignmentsTab({ assignmentsTabData, layoutType }) {
diff --git a/src/screens/Classroom/AssignmentsTab/styles.js b/src/screens/student/Classroom/AssignmentsTab/styles.js
index 8c97bd3..8c97bd3 100644
--- a/src/screens/Classroom/AssignmentsTab/styles.js
+++ b/src/screens/student/Classroom/AssignmentsTab/styles.js
diff --git a/src/screens/Classroom/Header/index.js b/src/screens/student/Classroom/Header/index.js
index 6a4a1a8..6a4a1a8 100644
--- a/src/screens/Classroom/Header/index.js
+++ b/src/screens/student/Classroom/Header/index.js
diff --git a/src/screens/Classroom/Header/styles.js b/src/screens/student/Classroom/Header/styles.js
index 58d19aa..58d19aa 100644
--- a/src/screens/Classroom/Header/styles.js
+++ b/src/screens/student/Classroom/Header/styles.js
diff --git a/src/screens/student/Classroom/PeopleTab/index.js b/src/screens/student/Classroom/PeopleTab/index.js
new file mode 100644
index 0000000..9dfde7b
--- /dev/null
+++ b/src/screens/student/Classroom/PeopleTab/index.js
@@ -0,0 +1,244 @@
+import { Avatar, Container, Skeleton, Stack, Typography } from '@mui/material';
+import { createArrayFrom1ToN } from '../../../../utils/createArrayFrom1ToN';
+import styles from './styles';
+
+function PeopleTab({ layoutType, peopleTabData }) {
+ const layoutResolver = (state, people, layoutType) => {
+ const {
+ externalContainer,
+ sectionContainer,
+ sectionTitle,
+ personContainer,
+ personAvatar,
+ personName,
+ emptyStateContainer,
+ } = styles[layoutType];
+ if (layoutType === 'desktop') {
+ switch (state) {
+ case 'loading':
+ return (
+ <Container sx={externalContainer} disableGutters>
+ <Stack alignItems="center">
+ <Skeleton
+ variant="rectangular"
+ width="90%"
+ height={70}
+ sx={{ marginBottom: '30px' }}
+ />
+ <Stack
+ flexDirection="row"
+ alignItems="center"
+ sx={{ width: '90%', marginLeft: '20px' }}
+ >
+ <Skeleton variant="circular" width={60} height={60} />
+ <Skeleton
+ variant="rectangular"
+ width="70%"
+ height={40}
+ sx={{ marginLeft: '15px' }}
+ />
+ </Stack>
+ </Stack>
+
+ <Stack alignItems="center">
+ <Skeleton
+ variant="rectangular"
+ width="90%"
+ height={70}
+ sx={{ marginBottom: '30px', marginTop: '50px' }}
+ />
+ {createArrayFrom1ToN(5).map(i => (
+ <Stack
+ key={i}
+ flexDirection="row"
+ alignItems="center"
+ sx={{ width: '90%', marginLeft: '20px', marginTop: '25px' }}
+ >
+ <Skeleton variant="circular" width={60} height={60} />
+ <Skeleton
+ variant="rectangular"
+ width="70%"
+ height={40}
+ sx={{ marginLeft: '15px' }}
+ />
+ </Stack>
+ ))}
+ </Stack>
+ </Container>
+ );
+ case 'idle':
+ const professors = people.filter(p => p.role === 'PROFESSOR');
+ const students = people.filter(p => p.role === 'STUDENT');
+
+ return (
+ <Container sx={externalContainer} disableGutters>
+ <Container sx={sectionContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h4">
+ Docentes
+ </Typography>
+ <Stack alignItems="center">
+ {professors.length !== 0 ? (
+ professors.map(p => (
+ <Container key={p.id} sx={personContainer} disableGutters>
+ <Avatar alt={p.name} src={p.avatar} sx={personAvatar} />
+ <Typography sx={personName} variant="h5">
+ {p.name}
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum professor encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+
+ <Container sx={sectionContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h4">
+ Discentes
+ </Typography>
+ <Stack alignItems="center">
+ {students.length !== 0 ? (
+ students.map(p => (
+ <Container key={p.id} sx={personContainer} disableGutters>
+ <Avatar alt={p.name} src={p.avatar} sx={personAvatar} />
+ <Typography sx={personName} variant="h5">
+ {p.name}
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum estudante encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+ </Container>
+ );
+ case 'gone':
+ return null;
+ default:
+ return null;
+ }
+ } else if (layoutType === 'mobile') {
+ switch (state) {
+ case 'loading':
+ return (
+ <Container sx={externalContainer} disableGutters>
+ <Stack alignItems="center">
+ <Skeleton
+ variant="rectangular"
+ width="90%"
+ height={50}
+ sx={{ marginBottom: '30px' }}
+ />
+ <Stack
+ flexDirection="row"
+ alignItems="center"
+ sx={{ width: '90%', marginLeft: '20px' }}
+ >
+ <Skeleton variant="circular" width={40} height={40} />
+ <Skeleton
+ variant="rectangular"
+ width="80%"
+ height={30}
+ sx={{ marginLeft: '15px' }}
+ />
+ </Stack>
+ </Stack>
+
+ <Stack alignItems="center">
+ <Skeleton
+ variant="rectangular"
+ width="90%"
+ height={50}
+ sx={{ marginBottom: '30px', marginTop: '50px' }}
+ />
+ {createArrayFrom1ToN(5).map(i => (
+ <Stack
+ key={i}
+ flexDirection="row"
+ alignItems="center"
+ sx={{ width: '90%', marginLeft: '20px', marginTop: '25px' }}
+ >
+ <Skeleton variant="circular" width={40} height={40} />
+ <Skeleton
+ variant="rectangular"
+ width="80%"
+ height={30}
+ sx={{ marginLeft: '15px' }}
+ />
+ </Stack>
+ ))}
+ </Stack>
+ </Container>
+ );
+ case 'idle':
+ const professors = people.filter(p => p.role === 'PROFESSOR');
+ const students = people.filter(p => p.role === 'STUDENT');
+
+ return (
+ <Container sx={externalContainer} disableGutters>
+ <Container sx={sectionContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h5">
+ Docentes
+ </Typography>
+ <Stack alignItems="center">
+ {professors.length !== 0 ? (
+ professors.map(p => (
+ <Container key={p.id} sx={personContainer} disableGutters>
+ <Avatar alt={p.name} src={p.avatar} sx={personAvatar} />
+ <Typography sx={personName} variant="body1">
+ {p.name}
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum professor encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+
+ <Container sx={sectionContainer} disableGutters>
+ <Typography sx={sectionTitle} variant="h5">
+ Discentes
+ </Typography>
+ <Stack alignItems="center">
+ {students.length !== 0 ? (
+ students.map(p => (
+ <Container key={p.id} sx={personContainer} disableGutters>
+ <Avatar alt={p.name} src={p.avatar} sx={personAvatar} />
+ <Typography sx={personName} variant="body1">
+ {p.name}
+ </Typography>
+ </Container>
+ ))
+ ) : (
+ <Container sx={emptyStateContainer} disableGutters>
+ <p>Nenhum estudante encontrado!</p>
+ </Container>
+ )}
+ </Stack>
+ </Container>
+ </Container>
+ );
+ case 'gone':
+ return null;
+ default:
+ return null;
+ }
+ }
+ };
+
+ return layoutResolver(
+ peopleTabData && peopleTabData.state,
+ peopleTabData && peopleTabData.people,
+ layoutType
+ );
+}
+
+export default PeopleTab;
diff --git a/src/screens/student/Classroom/PeopleTab/styles.js b/src/screens/student/Classroom/PeopleTab/styles.js
new file mode 100644
index 0000000..30668db
--- /dev/null
+++ b/src/screens/student/Classroom/PeopleTab/styles.js
@@ -0,0 +1,116 @@
+// ========== Desktop ==========
+const desktopExternalContainer = {
+ marginTop: '50px',
+ height: '100vh',
+};
+
+const desktopSectionContainer = {
+ width: '90%',
+ marginBottom: '30px',
+};
+
+const desktopSectionTitle = {
+ padding: '10px',
+ borderBottom: '2px solid #00420D',
+ color: '#00420D',
+};
+
+const desktopPersonContainer = {
+ width: '95%',
+ padding: '20px',
+ borderBottom: '2px solid #BCBCBC',
+ display: 'flex',
+ alignItems: 'center',
+};
+
+const desktopPersonAvatar = {
+ width: '60px',
+ height: '60px',
+ marginRight: '15px',
+};
+
+const desktopPersonName = {};
+
+const desktopEmptyStateContainer = {
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '30px',
+};
+
+const desktop = {
+ externalContainer: desktopExternalContainer,
+ sectionContainer: desktopSectionContainer,
+ sectionTitle: desktopSectionTitle,
+ personContainer: desktopPersonContainer,
+ personAvatar: desktopPersonAvatar,
+ personName: desktopPersonName,
+ emptyStateContainer: desktopEmptyStateContainer,
+};
+
+// ========== Mobile ==========
+const mobileExternalContainer = {
+ marginTop: '50px',
+ height: '100vh',
+};
+
+const mobileSectionContainer = {
+ width: '90%',
+ marginBottom: '30px',
+};
+
+const mobileSectionTitle = {
+ padding: '10px',
+ borderBottom: '2px solid #00420D',
+ color: '#00420D',
+};
+
+const mobilePersonContainer = {
+ width: '95%',
+ padding: '20px',
+ borderBottom: '2px solid #BCBCBC',
+ display: 'flex',
+ alignItems: 'center',
+};
+
+const mobilePersonAvatar = {
+ width: '40px',
+ height: '40px',
+ marginRight: '15px',
+};
+
+const mobilePersonName = {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ display: '-webkit-box',
+ WebkitLineClamp: 1,
+ WebkitBoxOrient: 'vertical',
+};
+
+const mobileEmptyStateContainer = {
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '30px',
+};
+
+const mobile = {
+ externalContainer: mobileExternalContainer,
+ sectionContainer: mobileSectionContainer,
+ sectionTitle: mobileSectionTitle,
+ personContainer: mobilePersonContainer,
+ personAvatar: mobilePersonAvatar,
+ personName: mobilePersonName,
+ emptyStateContainer: mobileEmptyStateContainer,
+};
+
+// ========== Unset ==========
+const unset = {
+ externalContainer: null,
+ sectionContainer: null,
+ sectionTitle: null,
+ personContainer: null,
+ personAvatar: null,
+ personName: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/screens/Classroom/View.js b/src/screens/student/Classroom/View.js
index 675e697..675e697 100644
--- a/src/screens/Classroom/View.js
+++ b/src/screens/student/Classroom/View.js
diff --git a/src/screens/Classroom/index.js b/src/screens/student/Classroom/index.js
index 7410515..5201b49 100644
--- a/src/screens/Classroom/index.js
+++ b/src/screens/student/Classroom/index.js
@@ -1,20 +1,14 @@
import { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
-import { useUser } from '../../context/user';
-import useLayoutType from '../../hooks/useLayoutType';
+import { useUser } from '../../../context/user';
+import useLayoutType from '../../../hooks/useLayoutType';
import { TAB_OPTIONS } from './tabOptions';
import View from './View';
function Classroom() {
const params = useParams();
const layoutType = useLayoutType();
- const {
- fetchClassroomById,
- fetchClassroomAnnouncements,
- fetchUpcomingAssignmentsByClassId,
- fetchAssignmentsByClassId,
- fetchPeopleByClassId,
- } = useUser();
+ const { userService } = useUser();
const [classroom, setClassroom] = useState(null);
const [tabData, setTabData] = useState(null);
const [selectedTabOption, setSelectedTabOption] = useState(
@@ -23,10 +17,11 @@ function Classroom() {
const fetchAndPopulateAnnouncementsTabData = useCallback(async () => {
setTabData({ tab: 'announcements', state: 'loading' });
- const announcements = await fetchClassroomAnnouncements(params.id);
- const upcomingAssignments = await fetchUpcomingAssignmentsByClassId(
+ const announcements = await userService.fetchClassroomAnnouncements(
params.id
);
+ const upcomingAssignments =
+ await userService.fetchUpcomingAssignmentsByClassId(params.id);
setTabData({
tab: 'announcements',
@@ -34,33 +29,29 @@ function Classroom() {
announcements: [...announcements.data],
upcomingAssignments: [...upcomingAssignments.data],
});
- }, [
- fetchClassroomAnnouncements,
- fetchUpcomingAssignmentsByClassId,
- params.id,
- ]);
+ }, [userService, params.id]);
const fetchAndPopulateAssignmentsTabData = useCallback(async () => {
setTabData({ tab: 'assignments', state: 'loading' });
- const assignments = await fetchAssignmentsByClassId(params.id);
+ const assignments = await userService.fetchAssignmentsByClassId(params.id);
setTabData({
tab: 'assignments',
state: 'idle',
assignments: [...assignments.data],
});
- }, [fetchAssignmentsByClassId, params.id]);
+ }, [userService, params.id]);
- const fetchAndPopulatePoepleTabData = useCallback(async () => {
+ const fetchAndPopulatePeopleTabData = useCallback(async () => {
setTabData({ tab: 'people', state: 'loading' });
- const people = await fetchPeopleByClassId(params.id);
+ const people = await userService.fetchPeopleByClassId(params.id);
setTabData({
tab: 'people',
state: 'idle',
people: [...people.data],
});
- }, [fetchPeopleByClassId, params.id]);
+ }, [userService, params.id]);
useEffect(() => {
async function getSelectedTabData() {
@@ -72,7 +63,7 @@ function Classroom() {
fetchAndPopulateAssignmentsTabData();
break;
case TAB_OPTIONS.people.value:
- fetchAndPopulatePoepleTabData();
+ fetchAndPopulatePeopleTabData();
break;
default:
console.log('Invalid tab option');
@@ -84,13 +75,13 @@ function Classroom() {
params,
fetchAndPopulateAnnouncementsTabData,
fetchAndPopulateAssignmentsTabData,
- fetchAndPopulatePoepleTabData,
+ fetchAndPopulatePeopleTabData,
]);
useEffect(() => {
async function getClassroomById(classId) {
document.title = 'Carregando...';
- const result = await fetchClassroomById(classId);
+ const result = await userService.fetchClassroomById(classId);
setClassroom(result.data);
}
@@ -102,7 +93,7 @@ function Classroom() {
getClassroomById(params.id);
updateDocumentTitle();
- }, [fetchClassroomById, params, classroom]);
+ }, [userService, userService.fetchClassroomById, params, classroom]);
return (
<View
diff --git a/src/screens/student/Classroom/styles.js b/src/screens/student/Classroom/styles.js
new file mode 100644
index 0000000..fe6018c
--- /dev/null
+++ b/src/screens/student/Classroom/styles.js
@@ -0,0 +1,34 @@
+// ========== Desktop ==========
+const desktopContainer = {
+ width: '100%',
+ height: '100vh',
+ backgroundColor: '#red',
+ padding: 0,
+ margin: 0,
+ marginTop: '50px',
+};
+
+const desktop = {
+ container: desktopContainer,
+};
+
+// ========== Mobile ==========
+const mobileContainer = {
+ width: '90%',
+ backgroundColor: '#red',
+ padding: 0,
+ marginTop: '30px',
+ paddingBottom: '100px',
+};
+
+const mobile = {
+ container: mobileContainer,
+};
+
+// ========== Unset ==========
+const unset = {
+ container: null,
+};
+
+const styles = { desktop, mobile, unset };
+export default styles;
diff --git a/src/screens/Classroom/tabOptions.js b/src/screens/student/Classroom/tabOptions.js
index 48fb518..48fb518 100644
--- a/src/screens/Classroom/tabOptions.js
+++ b/src/screens/student/Classroom/tabOptions.js
diff --git a/src/screens/Home/View.js b/src/screens/student/Home/View.js
index 749049d..27b0f78 100644
--- a/src/screens/Home/View.js
+++ b/src/screens/student/Home/View.js
@@ -1,10 +1,10 @@
import { Container, Grid, Skeleton, Stack } from '@mui/material';
-import ClassCard from '../../components/ClassCard';
-import AssignmentCard from '../../components/AssignmentCard';
+import ClassCard from '../../../components/ClassCard';
+import AssignmentCard from '../../../components/AssignmentCard';
import styles from './styles';
-import { createArrayFrom1ToN } from '../../utils/createArrayFrom1ToN';
+import { createArrayFrom1ToN } from '../../../utils/createArrayFrom1ToN';
function View({
layoutType,
diff --git a/src/screens/Home/index.js b/src/screens/student/Home/index.js
index 4c1cbc4..75d734e 100644
--- a/src/screens/Home/index.js
+++ b/src/screens/student/Home/index.js
@@ -1,33 +1,33 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import { useUser } from '../../context/user';
-import { useDocumentTitle } from '../../hooks/useDocumentTitle';
-import useLayoutType from '../../hooks/useLayoutType';
+import { useUser } from '../../../context/user';
+import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
+import useLayoutType from '../../../hooks/useLayoutType';
import View from './View';
function Home() {
useDocumentTitle('Página Inicial');
const navigate = useNavigate();
const layoutType = useLayoutType();
- const { fetchClassrooms, fetchAllAssignments } = useUser();
+ const { userService } = useUser();
const [classrooms, setClassrooms] = useState(null);
const [assignments, setAssignments] = useState(null);
useEffect(() => {
async function getClassrooms() {
- const result = await fetchClassrooms();
+ const result = await userService.fetchClassrooms();
setClassrooms(result.data);
}
getClassrooms();
- }, [fetchClassrooms]);
+ }, [userService, userService.fetchClassrooms]);
useEffect(() => {
async function getAssignments() {
- const result = await fetchAllAssignments();
+ const result = await userService.fetchAllAssignments();
setAssignments(result.data);
}
getAssignments();
- }, [fetchAllAssignments]);
+ }, [userService, userService.fetchAllAssignments]);
const onClickClassCard = id => {
navigate(`/class/${id}`);
diff --git a/src/screens/Home/styles.js b/src/screens/student/Home/styles.js
index 4ddefb9..4ddefb9 100644
--- a/src/screens/Home/styles.js
+++ b/src/screens/student/Home/styles.js
diff --git a/src/services/professor.js b/src/services/professor.js
new file mode 100644
index 0000000..584e119
--- /dev/null
+++ b/src/services/professor.js
@@ -0,0 +1,27 @@
+import { ProfessorApi } from '../utils/mocks/api';
+
+export default class ProfessorService {
+ constructor(user) {
+ this.user = user;
+ }
+
+ fetchClassrooms = () => ProfessorApi.getClassrooms(this.user.id);
+
+ fetchClassroomById = classId => ProfessorApi.getClassroomById(classId);
+
+ fetchAssignmentsByClassId = classId =>
+ ProfessorApi.getAssignmentsByClassId(classId);
+
+ fetchAssignmentsToReview = () =>
+ ProfessorApi.getAssignmentsToReview(this.user.id);
+
+ fetchClassroomAnnouncements = classId =>
+ ProfessorApi.getClassroomAnnouncementsById(classId);
+
+ fetchPeopleByClassId = classId => ProfessorApi.getPeopleByClassId(classId);
+
+ fetchGradesByClassId = classId => ProfessorApi.getGradesByClassId(classId);
+
+ fetchUpcomingAssignmentsByClassId = classId =>
+ ProfessorApi.getUpcomingAssignmentsByClassId(classId);
+}
diff --git a/src/services/provider.js b/src/services/provider.js
new file mode 100644
index 0000000..038bf5c
--- /dev/null
+++ b/src/services/provider.js
@@ -0,0 +1,31 @@
+export const UserServiceProvider = (function () {
+ let instance;
+
+ async function createInstance(user) {
+ switch (user.role) {
+ case 'STUDENT':
+ const studentService = await import('./student');
+ if (studentService) {
+ return new studentService.default(user);
+ }
+ break;
+ case 'PROFESSOR':
+ const professorService = await import('./professor');
+ if (professorService) {
+ return new professorService.default(user);
+ }
+ break;
+ default:
+ throw new Error('Invalid Role!');
+ }
+ }
+
+ return {
+ getInstance: async function (user) {
+ if (!instance) {
+ instance = await createInstance(user);
+ }
+ return instance;
+ },
+ };
+})();
diff --git a/src/services/student.js b/src/services/student.js
new file mode 100644
index 0000000..294edd3
--- /dev/null
+++ b/src/services/student.js
@@ -0,0 +1,29 @@
+import { StudentApi } from '../utils/mocks/api';
+
+export default class StudentService {
+ constructor(user) {
+ this.user = user;
+ }
+
+ fetchClassrooms = () => StudentApi.getClassrooms(this.user.id);
+
+ fetchAllAssignments = () => StudentApi.getAllAssignments(this.user.id);
+
+ fetchAssignmentById = assignmentId =>
+ StudentApi.getAssignmentById(assignmentId);
+
+ fetchAssignmentsByClassId = classId =>
+ StudentApi.getAssignmentsByClassId(classId);
+
+ fetchClassroomById = classId => StudentApi.getClassroomById(classId);
+
+ fetchFAQ = () => StudentApi.getFaq();
+
+ fetchClassroomAnnouncements = classId =>
+ StudentApi.getClassroomAnnouncementsById(classId);
+
+ fetchUpcomingAssignmentsByClassId = classId =>
+ StudentApi.getUpcomingAssignmentsByClassId(classId);
+
+ fetchPeopleByClassId = classId => StudentApi.getPeopleByClassId(classId);
+}
diff --git a/src/services/user-service.js b/src/services/user-service.js
deleted file mode 100644
index d256832..0000000
--- a/src/services/user-service.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import { sleep } from '../utils/sleep';
-import {
- allClassrooms,
- allAssignments,
- faq,
- user,
- authFailure,
- allClassroomAnnouncements,
- allUpcomingAssignments,
- allPeople,
-} from './mocks';
-
-const getClassrooms = userId =>
- sleep(300).then(() => {
- console.log('Get classrooms ' + userId);
- return {
- data: allClassrooms,
- };
- });
-
-const getClassroomById = classId =>
- sleep(300).then(() => {
- console.log('Get classroom by id ' + classId);
- return {
- data: allClassrooms.filter(c => c.id === classId)[0],
- };
- });
-
-const getClassroomAnnouncementsById = classId =>
- sleep(300).then(() => {
- console.log('Get classroon announcements by id ' + classId);
- return {
- data: allClassroomAnnouncements.filter(c => c.classroom.id === classId),
- };
- });
-
-const getUpcomingAssignmentsByClassId = classId =>
- sleep(300).then(() => {
- console.log('Getting upcoming assignments by class id ' + classId);
- return {
- data: allUpcomingAssignments.filter(
- a => a.classrooms.filter(c => c.id === classId)[0]
- ),
- };
- });
-
-const getAllAssignments = userId =>
- sleep(400).then(() => {
- console.log('Getting all assignments ' + userId);
- return {
- data: allAssignments,
- };
- });
-
-const getAssignmentById = assignmentId =>
- sleep(400).then(() => {
- console.log('Getting assignment by id ' + assignmentId);
- return {
- data: allAssignments.filter(a => a.id === assignmentId)[0],
- };
- });
-
-const getAssignmentsByClassId = classId =>
- sleep(300).then(() => {
- console.log('Getting assignments by class id ' + classId);
- return {
- data: allAssignments.filter(a => a.classrooms[0].id === classId),
- };
- });
-
-const getPeopleByClassId = classId =>
- sleep(400).then(() => {
- console.log('Getting people by class id ' + classId);
- return {
- data: allPeople.filter(p => p.classes[0].id === classId),
- };
- });
-
-const getFaq = () =>
- sleep(300).then(() => {
- console.log('Fetching FAQ...');
- return {
- data: faq,
- };
- });
-
-const getUser = shouldFail =>
- sleep(300).then(() => {
- if (shouldFail) {
- return authFailure;
- } else {
- window.localStorage.setItem('$USER', JSON.stringify(user));
- return user;
- }
- });
-
-const registerUser = (data, shouldFail) =>
- sleep(300).then(() => {
- if (shouldFail) {
- return authFailure;
- } else {
- console.log(data);
- window.localStorage.setItem('$USER', JSON.stringify(data));
- return data;
- }
- });
-
-export {
- getClassrooms,
- getClassroomById,
- getAllAssignments,
- getAssignmentById,
- getAssignmentsByClassId,
- getClassroomAnnouncementsById,
- getUpcomingAssignmentsByClassId,
- getPeopleByClassId,
- getFaq,
- getUser,
- registerUser,
-};
diff --git a/src/utils/mocks/api.js b/src/utils/mocks/api.js
new file mode 100644
index 0000000..6076993
--- /dev/null
+++ b/src/utils/mocks/api.js
@@ -0,0 +1,147 @@
+import { sleep } from '../sleep';
+import {
+ allClassrooms,
+ allAssignments,
+ faq,
+ studentUser,
+ professorUser,
+ authFailure,
+ allClassroomAnnouncements,
+ allUpcomingAssignments,
+ allPeople,
+ professorClassrooms,
+ assignmentsToReview,
+ grades,
+} from './responses';
+
+const CommonApi = {
+ getUser: (email, password) =>
+ sleep(300).then(() => {
+ let user;
+ if (email === 'p@test.com' && password === 'p123') {
+ user = professorUser;
+ } else if (email === 's@test.com' && password === 's123') {
+ user = studentUser;
+ } else {
+ return authFailure;
+ }
+ window.localStorage.setItem('$USER', JSON.stringify(user));
+ return user;
+ }),
+
+ registerUser: data =>
+ sleep(300).then(() => {
+ let userData;
+ if (data.email === 'p@test.com') {
+ userData = { ...data, role: 'PROFESSOR' };
+ } else if (data.email === 's@test.com') {
+ userData = { ...data, role: 'STUDENT' };
+ } else {
+ return authFailure;
+ }
+ window.localStorage.setItem('$USER', JSON.stringify(data));
+ return userData;
+ }),
+
+ getClassroomAnnouncementsById: classId =>
+ sleep(300).then(() => {
+ console.log('Get classroon announcements by id ' + classId);
+ return {
+ data: allClassroomAnnouncements.filter(c => c.classroom.id === classId),
+ };
+ }),
+ getClassroomById: classId =>
+ sleep(300).then(() => {
+ console.log('Get classroom by id ' + classId);
+ return {
+ data: allClassrooms.filter(c => c.id === classId)[0],
+ };
+ }),
+ getAssignmentsByClassId: classId =>
+ sleep(300).then(() => {
+ console.log('Getting assignments by class id ' + classId);
+ return {
+ data: allAssignments.filter(a => a.classrooms[0].id === classId),
+ };
+ }),
+
+ getPeopleByClassId: classId =>
+ sleep(400).then(() => {
+ console.log('Getting people by class id ' + classId);
+ return {
+ data: allPeople.filter(p => p.classes[0].id === classId),
+ };
+ }),
+
+ getUpcomingAssignmentsByClassId: classId =>
+ sleep(300).then(() => {
+ console.log('Getting upcoming assignments by class id ' + classId);
+ return {
+ data: allUpcomingAssignments.filter(
+ a => a.classrooms.filter(c => c.id === classId)[0]
+ ),
+ };
+ }),
+};
+
+const StudentApi = {
+ ...CommonApi,
+ getClassrooms: userId =>
+ sleep(300).then(() => {
+ console.log('Get classrooms ' + userId);
+ return {
+ data: allClassrooms,
+ };
+ }),
+
+ getAllAssignments: userId =>
+ sleep(400).then(() => {
+ console.log('Getting all assignments ' + userId);
+ return {
+ data: allAssignments,
+ };
+ }),
+
+ getAssignmentById: assignmentId =>
+ sleep(400).then(() => {
+ console.log('Getting assignment by id ' + assignmentId);
+ return {
+ data: allAssignments.filter(a => a.id === assignmentId)[0],
+ };
+ }),
+
+ getFaq: () =>
+ sleep(300).then(() => {
+ console.log('Fetching FAQ...');
+ return {
+ data: faq,
+ };
+ }),
+};
+
+const ProfessorApi = {
+ ...CommonApi,
+ getClassrooms: userId =>
+ sleep(300).then(() => {
+ console.log('Get classrooms ' + userId);
+ return {
+ data: professorClassrooms,
+ };
+ }),
+ getAssignmentsToReview: userId =>
+ sleep(400).then(() => {
+ console.log('Getting assignments to review' + userId);
+ return {
+ data: assignmentsToReview,
+ };
+ }),
+ getGradesByClassId: classId =>
+ sleep(400).then(() => {
+ console.log('Getting grades' + classId);
+ return {
+ data: grades,
+ };
+ }),
+};
+
+export { StudentApi, ProfessorApi, CommonApi };
diff --git a/src/services/mocks.js b/src/utils/mocks/responses.js
index fce3b29..44dd24c 100644
--- a/src/services/mocks.js
+++ b/src/utils/mocks/responses.js
@@ -4,6 +4,7 @@ const allClassrooms = [
name: 'Introdução à Ciência de Dados',
abbreviation: 'ICD',
color: '#006FF2',
+ virtualRoom: 'https://meet.jit.si/ifmg-icd-321',
teachers: [
{
id: '2342',
@@ -22,6 +23,7 @@ const allClassrooms = [
name: 'Gestão de Projetos',
abbreviation: 'GP',
color: '#7900F2',
+ virtualRoom: 'https://meet.jit.si/ifmg-gp-123',
teachers: [
{
id: '1234',
@@ -45,6 +47,7 @@ const allClassrooms = [
name: 'Banco de Dados II',
abbreviation: 'BDII',
color: '#FF7A00',
+ virtualRoom: 'https://meet.jit.si/ifmg-bdii-666',
teachers: [
{
id: '6781',
@@ -63,6 +66,7 @@ const allClassrooms = [
name: 'Contabilidade Básica',
abbreviation: 'CB',
color: '#BB0000',
+ virtualRoom: 'https://meet.jit.si/ifmg-cb-765',
teachers: [
{
id: '4321',
@@ -80,6 +84,7 @@ const allClassrooms = [
name: 'Linguagens de Programação',
abbreviation: 'LP',
color: '#039200',
+ virtualRoom: 'https://meet.jit.si/ifmg-lp-333',
teachers: [
{
id: '9999',
@@ -95,6 +100,44 @@ const allClassrooms = [
},
];
+const professorClassrooms = [
+ {
+ id: '321',
+ name: 'Introdução à Ciência de Dados',
+ abbreviation: 'ICD',
+ color: '#006FF2',
+ virtualRoom: 'https://meet.jit.si/ifmg-icd-321',
+ course: 'BSI 2020',
+ appointmentSlots: [
+ { weekDay: 'Quarta-feira', start: '10:00', end: '11:40' },
+ { weekDay: 'Sexta-feira', start: '10:00', end: '11:40' },
+ ],
+ },
+ {
+ id: '123',
+ name: 'Teoria dos Grafos',
+ abbreviation: 'TDG',
+ color: '#d30000',
+ virtualRoom: 'https://meet.jit.si/ifmg-tdg-123',
+ course: 'BSI 2018',
+ appointmentSlots: [
+ { weekDay: 'Quarta-feira', start: '11:00', end: '12:00' },
+ { weekDay: 'Segunda-feira', start: '10:00', end: '11:40' },
+ ],
+ },
+ {
+ id: '666',
+ name: 'Matemática Discreta',
+ abbreviation: 'MD',
+ color: '#149b00',
+ virtualRoom: 'https://meet.jit.si/ifmg-md-666',
+ course: 'BSI 2020',
+ appointmentSlots: [
+ { weekDay: 'Quarta-feira', start: '9:00', end: '10:00' },
+ ],
+ },
+];
+
const allAssignments = [
{
id: '5435',
@@ -309,6 +352,80 @@ const allClassroomAnnouncements = [
},
];
+const assignmentsToReview = [
+ {
+ id: '0123',
+ type: 'assessment',
+ title:
+ 'Prova 1 - Armazenamento de Dados. Python em CD. Armazenamento Analítico',
+ dueDate: '2022-07-01 23:59',
+ scores: [
+ {
+ classroomId: '321',
+ value: 30,
+ },
+ ],
+ classrooms: professorClassrooms.filter(c => c.id === '321'),
+ status: 'OPEN',
+ deliveredByStudents: 10,
+ reviewed: 6,
+ total: 30,
+ },
+ {
+ id: '0128',
+ type: 'assessment',
+ title:
+ 'Prova 2 - Visualização de Dados. Matemática e Estatística em CD. Análise de Dados',
+ dueDate: '2022-09-01 23:59',
+ scores: [
+ {
+ classroomId: '321',
+ value: 30,
+ },
+ ],
+ classrooms: professorClassrooms.filter(c => c.id === '321'),
+ status: 'CLOSED',
+ deliveredByStudents: 30,
+ reviewed: 1,
+ total: 30,
+ },
+ {
+ id: '0129',
+ type: 'assessment',
+ title: 'Lista de Exercícios 1 - Caminhos e circuitos',
+ dueDate: '2022-09-01 23:59',
+ scores: [
+ {
+ classroomId: '123',
+ value: 30,
+ },
+ ],
+ classrooms: professorClassrooms.filter(c => c.id === '123'),
+ status: 'CLOSED',
+ deliveredByStudents: 30,
+ reviewed: 0,
+ total: 30,
+ },
+
+ {
+ id: '0130',
+ type: 'assessment',
+ title: 'Lista de Exercícios 2 - Tabela verdade',
+ dueDate: '2022-09-01 23:59',
+ scores: [
+ {
+ classroomId: '666',
+ value: 30,
+ },
+ ],
+ classrooms: professorClassrooms.filter(c => c.id === '666'),
+ status: 'OPEN',
+ deliveredByStudents: 0,
+ reviewed: 0,
+ total: 30,
+ },
+];
+
const allUpcomingAssignments = [
{
id: '5435',
@@ -534,7 +651,14 @@ const allPeople = [
},
];
-const user = {
+// TODO: Mock correct data
+const grades = [
+ {
+ id: 'Some grade',
+ },
+];
+
+const studentUser = {
id: '123',
ra: '0021123',
username: 'ronaldosilva',
@@ -548,6 +672,25 @@ const user = {
course: 0,
termsAgreed: true,
year: 2018,
+ role: 'STUDENT',
+};
+
+const professorUser = {
+ id: '321',
+ ra: '0021123',
+ username: 'cazalbe',
+ email: 'carlos.junior@ifmg.edu.br',
+ password: '#carlos1234', // TODO: Remove this!
+ firstName: 'Carlos',
+ lastName: 'Alexandre Silva',
+ token: 'xkhfb9458hnsdfsi9q8345bsdf9b834yr',
+ phone: '31111111111',
+ avatar:
+ 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=50&q=80',
+ course: 0,
+ termsAgreed: true,
+ year: 2018,
+ role: 'PROFESSOR',
};
const authFailure = {
@@ -560,7 +703,11 @@ export {
allClassroomAnnouncements,
allPeople,
faq,
- user,
+ studentUser,
+ professorUser,
authFailure,
allUpcomingAssignments,
+ professorClassrooms,
+ assignmentsToReview,
+ grades,
};