commit
aad7f64bfe
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { Box, IconButton, NoSsr } from '@material-ui/core';
|
||||
import { Alert, AlertTitle } from '@material-ui/lab';
|
||||
import { Close as CloseIcon } from '@material-ui/icons';
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'showKeyboardNavigationNote';
|
||||
|
||||
const KeyboardNavigationNote = () => {
|
||||
const [show, setShow] = useLocalStorage(LOCAL_STORAGE_KEY, true);
|
||||
|
||||
if (!show || typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NoSsr>
|
||||
<Box mb={2}>
|
||||
<Alert
|
||||
severity="info"
|
||||
action={
|
||||
<IconButton onClick={() => setShow(false)}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<AlertTitle>Nawigacja</AlertTitle>
|
||||
Czy wiesz, że{' '}
|
||||
<strong>
|
||||
możesz poruszać się pomiędzy pytaniami i wybierać odpowiedzi{' '}
|
||||
</strong>
|
||||
za pomocą klawiatury? <br />- <strong>klawisze A / B / C / D</strong>{' '}
|
||||
- wybór odpowiedzi <br />- <strong>lewa strzałka</strong> - powrót do
|
||||
poprzedniego pytania <br />- <strong>prawa strzałka</strong> -
|
||||
przejście do następnego pytania <br />
|
||||
</Alert>
|
||||
</Box>
|
||||
</NoSsr>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeyboardNavigationNote;
|
|
@ -1,3 +1,5 @@
|
|||
import { useKeyPressEvent } from 'react-use';
|
||||
|
||||
import { makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
import { Button, ButtonGroup, useMediaQuery } from '@material-ui/core';
|
||||
import {
|
||||
|
@ -18,6 +20,16 @@ export interface NavigationProps {
|
|||
reviewMode: boolean;
|
||||
}
|
||||
|
||||
const NavigationKeyPressEvents = ({
|
||||
onRequestNextTab,
|
||||
onRequestPrevTab,
|
||||
}: Pick<NavigationProps, 'onRequestNextTab' | 'onRequestPrevTab'>) => {
|
||||
useKeyPressEvent('ArrowRight', onRequestNextTab);
|
||||
useKeyPressEvent('ArrowLeft', onRequestPrevTab);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Navigation = ({
|
||||
hasPreviousTab,
|
||||
hasNextTab,
|
||||
|
@ -31,6 +43,7 @@ const Navigation = ({
|
|||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const matches = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<div className={classes.buttonContainer}>
|
||||
<div className={classes.navButtonGroup}>
|
||||
|
@ -65,6 +78,10 @@ const Navigation = ({
|
|||
</Button>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
<NavigationKeyPressEvents
|
||||
onRequestNextTab={onRequestNextTab}
|
||||
onRequestPrevTab={onRequestPrevTab}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { useKeyPressEvent } from 'react-use';
|
||||
import clsx from 'clsx';
|
||||
import buildURL from 'utils/buildURL';
|
||||
import { Answer, Question as QuestionT } from 'libs/graphql';
|
||||
|
@ -15,18 +16,35 @@ export interface QuestionProps {
|
|||
answer: Answer;
|
||||
onSelectAnswer: (answer: Answer) => void;
|
||||
reviewMode: boolean;
|
||||
current: boolean;
|
||||
}
|
||||
|
||||
const ANSWERS = Object.values(Answer);
|
||||
|
||||
const QuestionKeyPressEvents = ({
|
||||
onSelectAnswer,
|
||||
}: Pick<QuestionProps, 'onSelectAnswer'>) => {
|
||||
useKeyPressEvent(
|
||||
e => {
|
||||
return (ANSWERS as string[]).includes(e.key.toLowerCase());
|
||||
},
|
||||
e => {
|
||||
onSelectAnswer(e.key.toLowerCase() as Answer);
|
||||
}
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
const Question = ({
|
||||
question,
|
||||
answer,
|
||||
onSelectAnswer,
|
||||
reviewMode,
|
||||
current,
|
||||
}: QuestionProps) => {
|
||||
const classes = useStyles();
|
||||
const updatedAt = new Date(question.updatedAt).getTime();
|
||||
|
||||
return (
|
||||
<div className={classes.question}>
|
||||
{question.from && (
|
||||
|
@ -111,6 +129,7 @@ const Question = ({
|
|||
</Button>
|
||||
);
|
||||
})}
|
||||
{current && <QuestionKeyPressEvents onSelectAnswer={onSelectAnswer} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState, Fragment } from 'react';
|
||||
import { useUpdateEffect } from 'react-use';
|
||||
import clsx from 'clsx';
|
||||
import { usePrompt } from 'libs/hooks';
|
||||
|
@ -30,6 +30,7 @@ import Question from './Question';
|
|||
import Navigation from './Navigation';
|
||||
import Summary from './Summary';
|
||||
import FixedSpinner from './FixedSpinner';
|
||||
import KeyboardNavigationNote from './KeyboardNavigationNote';
|
||||
|
||||
export interface TestProps {
|
||||
initialQuestions: QuestionT[];
|
||||
|
@ -50,6 +51,7 @@ const Test = ({ initialQuestions, qualification }: TestProps) => {
|
|||
const [startedAt, setStartedAt] = useState(new Date());
|
||||
const [endedAt, setEndedAt] = useState(new Date());
|
||||
const classes = useStyles();
|
||||
const maxTabIndex = questions.length + (reviewMode ? 1 : 0) - 1;
|
||||
usePrompt(!reviewMode);
|
||||
const analyticsParams = useMemo(
|
||||
() => ({
|
||||
|
@ -93,6 +95,10 @@ const Test = ({ initialQuestions, qualification }: TestProps) => {
|
|||
};
|
||||
|
||||
const handleSelectAnswer = (index: number, newAnswer: Answer) => {
|
||||
if (selectedAnswers[index] === newAnswer) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedAnswers(answers =>
|
||||
answers.map((oldAnswer, index2) =>
|
||||
index2 === index ? newAnswer : oldAnswer
|
||||
|
@ -107,6 +113,10 @@ const Test = ({ initialQuestions, qualification }: TestProps) => {
|
|||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
if (isFetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsFetching(true);
|
||||
const { generateTest: newQuestions } = await createClient().request<
|
||||
|
@ -136,6 +146,20 @@ const Test = ({ initialQuestions, qualification }: TestProps) => {
|
|||
setReviewMode(true);
|
||||
};
|
||||
|
||||
const handleGoToNextTab = () => {
|
||||
if (currentTab === maxTabIndex) {
|
||||
return;
|
||||
}
|
||||
setCurrentTab(current => current + 1);
|
||||
};
|
||||
|
||||
const handleGoToPrevTab = () => {
|
||||
if (currentTab === 0) {
|
||||
return;
|
||||
}
|
||||
setCurrentTab(current => current - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Section>
|
||||
{isFetching && <FixedSpinner />}
|
||||
|
@ -150,74 +174,76 @@ const Test = ({ initialQuestions, qualification }: TestProps) => {
|
|||
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}
|
||||
className={clsx(
|
||||
reviewMode
|
||||
? {
|
||||
[classes.correct]:
|
||||
question.correctAnswer ===
|
||||
selectedAnswers[index],
|
||||
[classes.incorrect]:
|
||||
question.correctAnswer !==
|
||||
selectedAnswers[index],
|
||||
}
|
||||
: {}
|
||||
)}
|
||||
label={`Pytanie ${index + 1}`}
|
||||
<Fragment>
|
||||
<KeyboardNavigationNote />
|
||||
<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}
|
||||
className={clsx(
|
||||
reviewMode
|
||||
? {
|
||||
[classes.correct]:
|
||||
question.correctAnswer ===
|
||||
selectedAnswers[index],
|
||||
[classes.incorrect]:
|
||||
question.correctAnswer !==
|
||||
selectedAnswers[index],
|
||||
}
|
||||
: {}
|
||||
)}
|
||||
label={`Pytanie ${index + 1}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Tab label="Podsumowanie" disabled={!reviewMode} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
<Box padding={3}>
|
||||
{questions.map((question, index) => (
|
||||
<TabPanel key={question.id} value={currentTab} index={index}>
|
||||
<Question
|
||||
question={question}
|
||||
answer={selectedAnswers[index]}
|
||||
onSelectAnswer={answer =>
|
||||
handleSelectAnswer(index, answer)
|
||||
}
|
||||
reviewMode={reviewMode}
|
||||
current={currentTab === index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Tab label="Podsumowanie" disabled={!reviewMode} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
<Box padding={3}>
|
||||
{questions.map((question, index) => (
|
||||
<TabPanel key={question.id} value={currentTab} index={index}>
|
||||
<Question
|
||||
question={question}
|
||||
answer={selectedAnswers[index]}
|
||||
onSelectAnswer={answer => handleSelectAnswer(index, answer)}
|
||||
</TabPanel>
|
||||
))}
|
||||
<TabPanel value={currentTab} index={questions.length}>
|
||||
<Summary
|
||||
answers={selectedAnswers}
|
||||
questions={questions}
|
||||
reviewMode={reviewMode}
|
||||
startedAt={startedAt}
|
||||
endedAt={endedAt}
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
<TabPanel value={currentTab} index={questions.length}>
|
||||
<Summary
|
||||
answers={selectedAnswers}
|
||||
questions={questions}
|
||||
<Navigation
|
||||
hasPreviousTab={currentTab !== 0}
|
||||
hasNextTab={currentTab !== maxTabIndex}
|
||||
onRequestPrevTab={handleGoToPrevTab}
|
||||
onRequestNextTab={handleGoToNextTab}
|
||||
isLastQuestion={currentTab === maxTabIndex}
|
||||
reviewMode={reviewMode}
|
||||
startedAt={startedAt}
|
||||
endedAt={endedAt}
|
||||
onReset={handleReset}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</TabPanel>
|
||||
<Navigation
|
||||
hasPreviousTab={currentTab !== 0}
|
||||
hasNextTab={
|
||||
currentTab + 1 !== questions.length + (reviewMode ? 1 : 0)
|
||||
}
|
||||
onRequestPrevTab={() => setCurrentTab(currentTab - 1)}
|
||||
onRequestNextTab={() => setCurrentTab(currentTab + 1)}
|
||||
isLastQuestion={
|
||||
currentTab + 1 === questions.length + (reviewMode ? 1 : 0)
|
||||
}
|
||||
reviewMode={reviewMode}
|
||||
onReset={handleReset}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Fragment>
|
||||
)}
|
||||
</Container>
|
||||
</Section>
|
||||
|
|
Reference in New Issue