This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
website/src/features/QualificationPage/features/TestPage/components/Test/Test.tsx

243 lines
7.3 KiB
TypeScript

import { Fragment, useRef, useState } from 'react';
import { useUpdateEffect } from 'react-use';
import clsx from 'clsx';
import { usePrompt } from 'libs/hooks';
import {
Answer,
createClient,
Qualification,
Query,
Question as QuestionT,
} from 'libs/graphql';
import { QUERY_GENERATE_TEST_SIMILAR_QUALIFICATIONS } from '../../queries';
import { QueryGenerateTestSimilarQualificationsArgs } from '../../types';
import { makeStyles } from '@material-ui/core/styles';
import {
AppBar,
Box,
Container,
Paper,
Tab,
Tabs,
Typography,
} from '@material-ui/core';
import Section from 'common/Section/Section';
import TabPanel from './TabPanel';
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[];
qualification: Qualification;
}
const MIN_QUESTIONS_REQUIRED_TO_SHOW_CONFIRMATION = 2;
const Test = ({ initialQuestions, qualification }: TestProps) => {
const headingRef = useRef<HTMLSpanElement | null>(null);
const [isFetching, setIsFetching] = useState(false);
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);
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);
useUpdateEffect(() => {
if (headingRef.current?.scrollIntoView) {
headingRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [currentTab]);
useUpdateEffect(() => {
resetValues(initialQuestions);
}, [qualification, initialQuestions]);
const resetValues = (
questions?: Pick<Query, 'generateTest'>['generateTest']
) => {
if (Array.isArray(questions)) {
setQuestions(questions);
}
setStartedAt(new Date());
setEndedAt(new Date());
setSelectedAnswers(
new Array((questions ?? initialQuestions).length).fill('')
);
setCurrentTab(0);
setReviewMode(false);
};
const handleSelectAnswer = (index: number, newAnswer: Answer) => {
if (selectedAnswers[index] === newAnswer) {
return;
}
setSelectedAnswers(answers =>
answers.map((oldAnswer, index2) =>
index2 === index ? newAnswer : oldAnswer
)
);
};
const handleReset = async () => {
if (isFetching) {
return;
}
try {
setIsFetching(true);
const { generateTest: newQuestions } = await createClient().request<
Pick<Query, 'generateTest'>,
QueryGenerateTestSimilarQualificationsArgs
>(QUERY_GENERATE_TEST_SIMILAR_QUALIFICATIONS, {
limitTest: questions.length,
limitSuggestions: 0,
skipSuggestions: true,
qualificationID: qualification.id,
});
resetValues(newQuestions);
} catch (e) {}
setIsFetching(false);
};
const handleFinish = () => {
if (
initialQuestions.length >= MIN_QUESTIONS_REQUIRED_TO_SHOW_CONFIRMATION &&
!window.confirm('Czy na pewno chcesz zakończyć test?')
) {
return;
}
setEndedAt(new Date());
setCurrentTab(currentTab => currentTab + 1);
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 />}
<Container>
<header>
<Typography ref={headingRef} align="center" variant="h1" gutterBottom>
Kwalifikacja <strong>{qualification.code}</strong>
</Typography>
</header>
{questions.length === 0 ? (
<Typography align="center" variant="h2">
Do tej kwalifikacji nie zostały dodane żadne pytania.
</Typography>
) : (
<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}
/>
</TabPanel>
))}
<TabPanel value={currentTab} index={questions.length}>
<Summary
answers={selectedAnswers}
questions={questions}
reviewMode={reviewMode}
startedAt={startedAt}
endedAt={endedAt}
/>
</TabPanel>
<Navigation
hasPreviousTab={currentTab !== 0}
hasNextTab={currentTab !== maxTabIndex}
onRequestPrevTab={handleGoToPrevTab}
onRequestNextTab={handleGoToNextTab}
isLastQuestion={currentTab === maxTabIndex}
reviewMode={reviewMode}
onReset={handleReset}
onFinish={handleFinish}
/>
</Box>
</Paper>
</Fragment>
)}
</Container>
</Section>
);
};
const useStyles = makeStyles(theme => {
return {
incorrect: {
color: `${theme.palette.error.main} !important`,
},
correct: {
color: `${theme.palette.success.main} !important`,
},
};
});
export default Test;