[TestPage] add Test component

This commit is contained in:
Dawid Wysokiński 2021-03-28 17:05:40 +02:00
parent c21c8eda7d
commit 56e1381631
6 changed files with 258 additions and 0 deletions

2
src/config/cdn.ts Normal file
View File

@ -0,0 +1,2 @@
export const CDN_URI =
process.env.NEXT_PUBLIC_CDN_URI ?? 'http://localhost:9000/';

View File

@ -19,6 +19,7 @@ import {
import Layout from 'common/Layout/Layout';
import SEO from 'common/SEO/SEO';
import Suggestions from './components/Suggestions/Suggestions';
import Test from './components/Test/Test';
export type TestPageParams = {
slug: string;
@ -40,6 +41,7 @@ const TestPage = ({ questions, suggestions, qualification }: TestPageProps) => {
questions.length
} ${polishPlurals('pytanie', 'pytania', 'pytań', questions.length)}`}
/>
<Test initialQuestions={questions} qualification={qualification} />
<Suggestions suggestions={suggestions} />
</Layout>
);

View File

@ -0,0 +1,143 @@
import React, { Fragment } from 'react';
import clsx from 'clsx';
import buildURL from 'utils/buildURL';
import { Answer, Question as QuestionT } from 'libs/graphql';
import { makeStyles } from '@material-ui/core/styles';
import { Typography, Button } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
export interface QuestionProps {
question: QuestionT;
answer: Answer;
onChangeAnswer: (answer: Answer) => void;
reviewMode: boolean;
}
function Question({
question,
answer,
onChangeAnswer,
reviewMode,
}: QuestionProps) {
const classes = useStyles();
return (
<div className={classes.question}>
{question.from && (
<Typography variant="caption">{question.from}</Typography>
)}
<Typography variant="h4" component="h3">
{question.content.split('\n').map(fragment => {
return (
<Fragment key={fragment}>
{fragment}
<br />
</Fragment>
);
})}
</Typography>
{question.image && (
<Typography align="center">
<img src={buildURL('cdn', question.image)} alt={question.content} />
</Typography>
)}
{reviewMode && (
<div className={classes.alertContainer}>
{!answer && (
<Alert severity="error">
Nie wybrałeś w tym pytaniu żadnej odpowiedzi.
</Alert>
)}
{question.explanation && (
<Alert severity="info">{question.explanation}</Alert>
)}
</div>
)}
{Object.values(Answer).map(a => {
const upper = a.toUpperCase();
const image = question[
`answer${upper}Image` as keyof QuestionT
] as string;
const answerContent = question[
`answer${upper}` as keyof QuestionT
] as string;
return (
<Button
key={a}
className={clsx(classes.button, {
[classes.buttonWithImage]: !!image,
correct: reviewMode && a === question.correctAnswer,
wrong: reviewMode && a !== question.correctAnswer && a === answer,
})}
variant={
a === answer || (reviewMode && a === question.correctAnswer)
? 'contained'
: 'outlined'
}
fullWidth
onClick={() => onChangeAnswer(a)}
color={'primary'}
>
{`${upper}. `}
{image ? (
<img
src={buildURL('cdn', image)}
alt={answerContent ?? `Odpowiedź ${upper}.`}
/>
) : (
answerContent.split('\n').map(fragment => (
<Fragment key={fragment}>
{fragment}
<br />
</Fragment>
))
)}
</Button>
);
})}
</div>
);
}
const useStyles = makeStyles(theme => {
return {
question: {
'& > *:not(:last-child)': {
marginBottom: theme.spacing(2),
},
'& img': {
maxWidth: '100%',
},
},
button: {
justifyContent: 'flex-start',
transition: 'background-color .2s',
textAlign: 'left',
textTransform: 'none',
'& img ': {
display: 'block',
},
'&.correct': {
backgroundColor: theme.palette.success.main,
color: theme.palette.success.contrastText,
},
'&.wrong': {
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText,
},
},
buttonWithImage: {
'& .MuiButton-label': {
flexDirection: 'column',
alignItems: 'flex-start',
},
},
alertContainer: {
'& > *:not(:last-child)': {
marginBottom: theme.spacing(1),
},
},
};
});
export default Question;

View File

@ -0,0 +1,18 @@
export interface TabPanelProps {
children?: React.ReactNode;
value: number;
index: number;
}
const TabPanel = ({ children, value, index }: TabPanelProps) => {
return (
<div
hidden={value !== index}
style={{ display: value === index ? 'block' : 'none' }}
>
{children}
</div>
);
};
export default TabPanel;

View File

@ -0,0 +1,82 @@
import { useState } from 'react';
import { Answer, Qualification, Question as QuestionT } from 'libs/graphql';
import {
Container,
Paper,
Tab,
Typography,
Tabs,
AppBar,
Box,
} from '@material-ui/core';
import Section from 'common/Section/Section';
import TabPanel from './TabPanel';
import Question from './Question';
export interface TestProps {
initialQuestions: QuestionT[];
qualification: Qualification;
}
const Test = ({ initialQuestions, qualification }: TestProps) => {
const [questions, setQuestions] = useState(initialQuestions);
const [selectedAnswers, setSelectedAnswers] = useState<Answer[]>(
new Array(initialQuestions.length).fill('')
);
const [currentTab, setCurrentTab] = useState(0);
const [reviewMode, setReviewMode] = useState(false);
return (
<Section>
<Container>
<Typography align="center" variant="h1" gutterBottom>
Kwalifikacja <strong>{qualification.code}</strong>
</Typography>
{questions.length === 0 ? (
<Typography align="center" variant="h2">
Do tej kwalifikacji nie zostały dodane żadne pytania.
</Typography>
) : (
<Paper>
<AppBar color="default" position="static">
<Tabs
value={currentTab}
textColor="primary"
indicatorColor="primary"
variant="scrollable"
onChange={(_, newTab: number) => setCurrentTab(newTab)}
>
{questions.map((question, index) => {
return (
<Tab key={question.id} label={`Pytanie ${index + 1}`} />
);
})}
</Tabs>
</AppBar>
<Box padding={3}>
{questions.map((question, index) => (
<TabPanel key={question.id} value={index} index={index}>
<Question
question={question}
answer={selectedAnswers[index]}
onChangeAnswer={newAnswer =>
setSelectedAnswers(answers =>
answers.map((oldAnswer, index2) =>
index2 === index ? newAnswer : oldAnswer
)
)
}
reviewMode={reviewMode}
/>
</TabPanel>
))}
</Box>
</Paper>
)}
</Container>
</Section>
);
};
export default Test;

11
src/utils/buildURL.ts Normal file
View File

@ -0,0 +1,11 @@
import { CDN_URI } from 'config/cdn';
const buildURL = (type: 'cdn', str: string): string => {
switch (type) {
case 'cdn':
return CDN_URI + str;
}
return str;
};
export default buildURL;