summaryrefslogtreecommitdiff
path: root/src/screens/professor
diff options
context:
space:
mode:
authorLeonardo Murça <106257713+leomurca@users.noreply.github.com>2023-02-07 20:40:41 -0300
committerGitHub <noreply@github.com>2023-02-07 20:40:41 -0300
commit8eca8b79ce4bfc40f8416309cbcfe397ed935ec4 (patch)
treeca4152122b67605e76f7a53ed1a14402255b23aa /src/screens/professor
parente3de3c8e5fe06f7d14b2dc99a8b6aadc3b9bf18a (diff)
parentc1f1286c86a47b87abcca55cbd0177d2a9c92fcd (diff)
Merge pull request #20 from leomurca/feature/professor_classroomHEADmain
Feature/professor classroom
Diffstat (limited to 'src/screens/professor')
-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.js244
-rw-r--r--src/screens/professor/Classroom/PeopleTab/styles.js116
-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.js34
-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
17 files changed, 2221 insertions, 0 deletions
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/professor/Classroom/PeopleTab/index.js b/src/screens/professor/Classroom/PeopleTab/index.js
new file mode 100644
index 0000000..9dfde7b
--- /dev/null
+++ b/src/screens/professor/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/professor/Classroom/PeopleTab/styles.js b/src/screens/professor/Classroom/PeopleTab/styles.js
new file mode 100644
index 0000000..30668db
--- /dev/null
+++ b/src/screens/professor/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/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/professor/Classroom/styles.js b/src/screens/professor/Classroom/styles.js
new file mode 100644
index 0000000..fe6018c
--- /dev/null
+++ b/src/screens/professor/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/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;