Skip to content

Commit

Permalink
15 Reviewer Step 1: Overview and EOC (#90)
Browse files Browse the repository at this point in the history
* refactor review progress steps

* refactoring the way we get `courseEvaluation` by wrapping from SWR

* review steps with description

* bootstrap content for all reviewer pages

* initial working on the overview and EOC page

* installation of MUI lab

* EOC ACcordion for Refresher

* frontend linting

* reviewer bottom navigation

* submission to the backend

* linting fix

* update poetry packages
  • Loading branch information
frinzekt authored Aug 31, 2022
1 parent 816e1f3 commit 56bb80f
Show file tree
Hide file tree
Showing 19 changed files with 716 additions and 264 deletions.
304 changes: 169 additions & 135 deletions backend/poetry.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import React, { useState } from 'react';
import {
API_ENDPOINT,
CourseEvaluationDetailEntry,
DEFAULT_COURSE_EVALUTION_DETAIL_ENTRY,
Document,
} from 'utils/api';
import { API_ENDPOINT, Document } from 'utils/api';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Dialog from '@mui/material/Dialog';
Expand All @@ -24,27 +19,23 @@ import Checkbox from '@mui/material/Checkbox';
import Autocomplete from '@mui/material/Autocomplete';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import useSWRAuth from '@/components/hooks/useSWRAuth';
import useAuthenticatedAPIClient from '@/components/hooks/useAuthenticatedAPIClient';
import { compileAllTheEOCSpecificsOfAnEOCSet } from '@/components/utils/eoc';
import useCourseEvaluation from '@/components/hooks/useCourseEvaluation';

const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;

type Props = {
// The reason why we cannot just use `document.courseEvaluation` is that we may use `create` functionality which means we don't have the course evaluation id.
courseEvaluationId: string;
document?: Document;
handleClose: () => void;
};

const EditGeneralInformationModal = (props: Props) => {
const { courseEvaluationId, document, handleClose } = props;
const { response } = useSWRAuth(
courseEvaluationId ? API_ENDPOINT.COURSE_EVALUATION.DETAIL(courseEvaluationId) : '',
);

const courseEvaluation = ((response?.data as unknown) ||
DEFAULT_COURSE_EVALUTION_DETAIL_ENTRY) as CourseEvaluationDetailEntry;
const { courseEvaluation } = useCourseEvaluation();

const eocGenerals = courseEvaluation.eoc_set.eoc_generals || [];
const eocSpecifics = compileAllTheEOCSpecificsOfAnEOCSet(courseEvaluation.eoc_set);
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/CourseEvaluation/Overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const Overview = ({ evaluation }: Props) => {
/>
<CardContent>
<Typography sx={{ p: 1, fontWeight: 'bold' }}>Course Description:</Typography>
<Typography sx={{ p: 1 }} component="span" variant="subtitle2">
<Typography sx={{ p: 1 }} variant="subtitle2">
{evaluation.description}
</Typography>
<Typography sx={{ p: 1, fontWeight: 'bold' }}>
Expand Down
36 changes: 15 additions & 21 deletions frontend/components/CourseEvaluation/SectionTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,42 @@ import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Container from '@mui/material/Container';
import {
API_ENDPOINT,
CourseEvaluationDetailEntry,
DEFAULT_COURSE_EVALUTION_DETAIL_ENTRY,
} from 'utils/api';
import ArticleIcon from '@mui/icons-material/Article';
import AssignmentIcon from '@mui/icons-material/Assignment';
import RateReviewIcon from '@mui/icons-material/RateReview';
import CampaignIcon from '@mui/icons-material/Campaign';
import useSWRAuth from '@/components/hooks/useSWRAuth';

import TabPanel, { a11yProps } from '../Custom/TabPanel';
import Overview from './Overview';
import Justification from './Justifications';
import Reviews from './Reviews';
import Documents from './Documents';
import useCourseEvaluation from '../hooks/useCourseEvaluation';

type Props = {
courseEvaluationId: string;
};

const SectionTabs = ({ courseEvaluationId }: Props) => {
const { response } = useSWRAuth(
courseEvaluationId ? API_ENDPOINT.COURSE_EVALUATION.DETAIL(courseEvaluationId) : '',
);

const evaluation = ((response?.data as unknown) ||
DEFAULT_COURSE_EVALUTION_DETAIL_ENTRY) as CourseEvaluationDetailEntry;
const SectionTabs = () => {
const { courseEvaluation } = useCourseEvaluation();

const TAB_DISPLAYS = [
{
label: 'Overview',
tabComponent: <Overview evaluation={evaluation} />,
tabComponent: <Overview evaluation={courseEvaluation} />,
icon: AssignmentIcon,
},
{ label: 'Documents', tabComponent: <Documents evaluation={evaluation} />, icon: ArticleIcon },
{
label: 'Documents',
tabComponent: <Documents evaluation={courseEvaluation} />,
icon: ArticleIcon,
},
{
label: 'Justification',
tabComponent: <Justification evaluation={evaluation} />,
tabComponent: <Justification evaluation={courseEvaluation} />,
icon: CampaignIcon,
},
{ label: 'Reviews', tabComponent: <Reviews evaluation={evaluation} />, icon: RateReviewIcon },
{
label: 'Reviews',
tabComponent: <Reviews evaluation={courseEvaluation} />,
icon: RateReviewIcon,
},
];

const [tabsValue, setTabsValue] = React.useState(0);
Expand Down
27 changes: 27 additions & 0 deletions frontend/components/Reviewer/AboutStepCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import { getReviewStepsWithState } from '@/components/utils/reviews';
import Container from '@mui/material/Container';

type Props = {
stepIndex: number;
};

const AboutStepCard = (props: Props) => {
const { stepIndex } = props;
const stepDetails = getReviewStepsWithState()[stepIndex];
return (
<Container maxWidth="xl" sx={{ mt: 2, mb: 2 }}>
<Card>
<CardHeader
title={`Step ${stepDetails.stepNo}: ${stepDetails.stepName}`}
subheader={stepDetails.description}
/>
</Card>
</Container>
);
};

export default AboutStepCard;
27 changes: 0 additions & 27 deletions frontend/components/Reviewer/OngoingReviewsList.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import Typography from '@mui/material/Typography';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import React, { useState } from 'react';
import { EocSetEocGeneral } from 'utils/api';
import Tab from '@mui/material/Tab';
import TabContext from '@mui/lab/TabContext';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';

type Props = {
eocGeneral: EocSetEocGeneral;
};

const EOCAccordionForRefresher = ({ eocGeneral }: Props) => {
const [value, setValue] = useState('0');
const handleChange = (event: React.SyntheticEvent, newValue: React.SetStateAction<string>) => {
setValue(newValue);
};

return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>{`EOC ${eocGeneral.number}: ${eocGeneral.title}`}</Typography>
</AccordionSummary>
<AccordionDetails>
<TabContext value={`${value}`}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={handleChange}>
{eocGeneral.eoc_specifics.map((eocSpecific, index) => (
<Tab
key={eocSpecific.id}
value={`${index}`}
label={`EOC ${eocSpecific.general_and_specific_eoc}`}
/>
))}
</TabList>
</Box>
{eocGeneral.eoc_specifics.map((eocSpecific, index) => (
<TabPanel key={eocSpecific.id} value={`${index}`}>
<Typography gutterBottom>
<strong>Description:</strong> {eocSpecific.description}
</Typography>
<Typography>
<strong>Indicators of Attainment:</strong>
</Typography>
<List>
{eocSpecific.indicators_of_attainment.map((indicator, indicatorIndex) => (
// eslint-disable-next-line react/no-array-index-key
<ListItem key={indicatorIndex} sx={{ display: 'list-item' }}>
{indicator}
</ListItem>
))}
</List>
</TabPanel>
))}
</TabContext>
</AccordionDetails>
</Accordion>
);
};

export default EOCAccordionForRefresher;
34 changes: 4 additions & 30 deletions frontend/components/Reviewer/ReviewProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ReviewListEntry } from 'utils/api';
import { styled } from '@mui/material/styles';
import StepConnector, { stepConnectorClasses } from '@mui/material/StepConnector';
import { Box } from '@mui/system';
import { determineStepsStateOfReview } from '../utils/reviews';
import { getReviewStepsWithState } from '../utils/reviews';

const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`&.${stepConnectorClasses.alternativeLabel}`]: {
Expand Down Expand Up @@ -87,40 +87,14 @@ const ReviewProgress = ({ review, isCoordinator }: Props) => {
* 2 (Commented on an EOC Specific)
* 3 (Submitted the review)
*/
const stateOfReview = determineStepsStateOfReview(review);
const steps = [
{
stepName: 'Overview & Eoc',
done: stateOfReview.step1,
stepLink: 'overview-and-eoc',
},
{
stepName: 'Read Documents',
done: stateOfReview.step2,
stepLink: 'documents',
},

{
stepName: 'Review Course',
done: stateOfReview.step3,
stepLink: 'assessment',
},

{
stepName: 'Review & Submit',
done: stateOfReview.step4,
stepLink: 'submit',
},
];
const steps = getReviewStepsWithState(review);
return (
<Stepper alternativeLabel nonLinear connector={<ColorlibConnector />}>
{steps.map(({ stepName, done = false, stepLink }, index) => (
{steps.map(({ stepName, done = false, fullLink }) => (
<Step key={stepName} active={done}>
<StepButton
icon={StepIcon({ done })}
onClick={() =>
!isCoordinator && router.push(`/review/${review.id}/${index + 1}-${stepLink}`)
}
onClick={() => !isCoordinator && router.push(fullLink)}
>
{stepName}
</StepButton>
Expand Down
59 changes: 59 additions & 0 deletions frontend/components/Reviewer/ReviewerBottomNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import Container from '@mui/material/Container';
import Button from '@mui/material/Button';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore';
import { useRouter } from 'next/router';

type Props = {
previousLink?: string;
nextLink?: string;
handleSubmit?: () => void;
};
const ReviewerBottomNavigation = (props: Props) => {
const { previousLink, nextLink, handleSubmit } = props;

const router = useRouter();
const handlePrevious = () => {
if (previousLink) {
router.push(previousLink);
}
};
const handleNext = () => {
if (nextLink) {
router.push(nextLink);
}
if (handleSubmit) {
handleSubmit();
}
};

// If the previous link is not there, then we have to not show it
const numberOfItems = previousLink ? 2 : 1;
return (
<Container
maxWidth="xl"
sx={{
display: 'flex',
justifyContent: numberOfItems > 1 ? 'space-between' : 'flex-end',
}}
>
{previousLink && (
<Button startIcon={<NavigateBeforeIcon />} variant="contained" onClick={handlePrevious}>
Previous
</Button>
)}
<Button endIcon={<NavigateNextIcon />} variant="contained" onClick={handleNext}>
{nextLink ? 'Next' : 'Submit'}
</Button>
</Container>
);
};

ReviewerBottomNavigation.defaultProps = {
previousLink: '',
nextLink: '',
handleSubmit: () => {},
};

export default ReviewerBottomNavigation;
25 changes: 25 additions & 0 deletions frontend/components/hooks/useCourseEvaluation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useRouter } from 'next/router';
import {
API_ENDPOINT,
CourseEvaluationDetailEntry,
DEFAULT_COURSE_EVALUTION_DETAIL_ENTRY,
} from 'utils/api';
import useSWRAuth from './useSWRAuth';

/**
* Helper hook that pulls out the course evaluation id from the url and returns the SWR value for it
*/
const useCourseEvaluation = (overwriteCourseEvalutionId?: string) => {
const router = useRouter();
const courseEvaluationId = (overwriteCourseEvalutionId || router.query?.id || '') as string;

const swrResult = useSWRAuth(
courseEvaluationId ? API_ENDPOINT.COURSE_EVALUATION.DETAIL(courseEvaluationId) : '',
);

const courseEvaluation = ((swrResult.response?.data as unknown) ||
DEFAULT_COURSE_EVALUTION_DETAIL_ENTRY) as CourseEvaluationDetailEntry;
return { courseEvaluation, swr: swrResult };
};

export default useCourseEvaluation;
19 changes: 19 additions & 0 deletions frontend/components/hooks/useCourseReview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useRouter } from 'next/router';
import { API_ENDPOINT, DEFAULT_REVIEW_LIST_ENTRY, ReviewListEntry } from 'utils/api';
import useSWRAuth from './useSWRAuth';

/**
* Helper hook that pulls out the review id from the url and returns the SWR value for it
*/
const useCourseReview = () => {
const router = useRouter();
const reviewId = (router.query?.reviewId || '') as string;

const swrResult = useSWRAuth(reviewId ? API_ENDPOINT.REVIEWS.DETAIL(reviewId) : '');

const courseReview = ((swrResult.response?.data as unknown) ||
DEFAULT_REVIEW_LIST_ENTRY) as ReviewListEntry;
return { courseReview, swr: swrResult };
};

export default useCourseReview;
Loading

0 comments on commit 56bb80f

Please sign in to comment.