[TestPage] add Test component
This commit is contained in:
parent
c21c8eda7d
commit
56e1381631
|
@ -0,0 +1,2 @@
|
|||
export const CDN_URI =
|
||||
process.env.NEXT_PUBLIC_CDN_URI ?? 'http://localhost:9000/';
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
Reference in New Issue