From af2cb9536caede49b9426750a24390e01e2b6382 Mon Sep 17 00:00:00 2001 From: Kichiyaki Date: Sun, 28 Mar 2021 18:28:53 +0200 Subject: [PATCH] [TestPage] add two new components - Navigation and Summary --- .../TestPage/components/Test/Navigation.tsx | 98 +++++++++++++++++++ .../TestPage/components/Test/Question.tsx | 6 +- .../TestPage/components/Test/Summary.tsx | 55 +++++++++++ .../TestPage/components/Test/Test.tsx | 63 +++++++++++- .../features/TestPage/queries.ts | 1 + src/libs/hooks/useCountdown.ts | 6 +- 6 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 src/features/QualificationPage/features/TestPage/components/Test/Navigation.tsx create mode 100644 src/features/QualificationPage/features/TestPage/components/Test/Summary.tsx diff --git a/src/features/QualificationPage/features/TestPage/components/Test/Navigation.tsx b/src/features/QualificationPage/features/TestPage/components/Test/Navigation.tsx new file mode 100644 index 0000000..f6998ef --- /dev/null +++ b/src/features/QualificationPage/features/TestPage/components/Test/Navigation.tsx @@ -0,0 +1,98 @@ +import { makeStyles, useTheme } from '@material-ui/core/styles'; +import { Button, ButtonGroup, useMediaQuery } from '@material-ui/core'; +import { + ArrowBack as ArrowBackIcon, + ArrowForward as ArrowForwardIcon, + Done as DoneIcon, + Refresh as RefreshIcon, +} from '@material-ui/icons'; + +export interface NavigationProps { + hasPreviousTab: boolean; + hasNextTab: boolean; + isLastQuestion: boolean; + onRequestPrevTab: () => void; + onRequestNextTab: () => void; + onFinish: () => void; + onReset: () => void; + reviewMode: boolean; +} + +const Navigation = ({ + hasPreviousTab, + hasNextTab, + isLastQuestion, + onRequestPrevTab, + onRequestNextTab, + onFinish, + reviewMode, + onReset, +}: NavigationProps) => { + const classes = useStyles(); + const theme = useTheme(); + const matches = useMediaQuery(theme.breakpoints.down('sm')); + return ( +
+
+ + +
+ + {isLastQuestion && !reviewMode && ( + + )} + {isLastQuestion && reviewMode && ( + + )} + +
+ ); +}; + +const useStyles = makeStyles(theme => { + return { + navButtonGroup: { + display: 'flex', + '& > *:not(:last-child)': { + marginRight: theme.spacing(2), + }, + [theme.breakpoints.down('sm')]: { + justifyContent: 'space-between', + }, + }, + + buttonContainer: { + marginTop: theme.spacing(3), + display: 'flex', + justifyContent: 'space-between', + [theme.breakpoints.down('sm')]: { + flexDirection: 'column', + '& > *:not(:last-child)': { + marginBottom: theme.spacing(1), + }, + }, + }, + }; +}); + +export default Navigation; diff --git a/src/features/QualificationPage/features/TestPage/components/Test/Question.tsx b/src/features/QualificationPage/features/TestPage/components/Test/Question.tsx index d910c17..7849cd0 100644 --- a/src/features/QualificationPage/features/TestPage/components/Test/Question.tsx +++ b/src/features/QualificationPage/features/TestPage/components/Test/Question.tsx @@ -16,12 +16,12 @@ export interface QuestionProps { reviewMode: boolean; } -function Question({ +const Question = ({ question, answer, onChangeAnswer, reviewMode, -}: QuestionProps) { +}: QuestionProps) => { const classes = useStyles(); return (
@@ -109,7 +109,7 @@ function Question({ })}
); -} +}; const useStyles = makeStyles(theme => { return { diff --git a/src/features/QualificationPage/features/TestPage/components/Test/Summary.tsx b/src/features/QualificationPage/features/TestPage/components/Test/Summary.tsx new file mode 100644 index 0000000..7f6050f --- /dev/null +++ b/src/features/QualificationPage/features/TestPage/components/Test/Summary.tsx @@ -0,0 +1,55 @@ +import { useMemo } from 'react'; +import { polishPlurals } from 'polish-plurals'; +import { calculateDifferenceBetweenTwoDates } from 'libs/hooks'; +import { Answer, Question } from 'libs/graphql'; + +import { Typography } from '@material-ui/core'; + +export interface SummaryProps { + answers: Answer[]; + questions: Question[]; + reviewMode: boolean; + startedAt: Date; + endedAt: Date; +} + +const Summary = ({ + answers, + questions, + reviewMode, + startedAt, + endedAt, +}: SummaryProps) => { + const correctAnswers = useMemo(() => { + if (!reviewMode) return 0; + return answers.filter( + (answer, index) => questions[index].correctAnswer === answer + ).length; + }, [answers, questions, reviewMode]); + const { hours, seconds, minutes } = useMemo(() => { + return calculateDifferenceBetweenTwoDates(startedAt, endedAt); + }, [startedAt, endedAt]); + const total = questions.length; + + return ( +
+ + Czas rozwiązywania: {hours}{' '} + {polishPlurals('godzina', 'godziny', 'godzin', hours)}, {minutes}{' '} + {polishPlurals('minuta', 'minuty', 'minut', minutes)} i {seconds}{' '} + {polishPlurals('sekunda', 'sekundy', 'sekund', seconds)}. + + + Twój wynik:{' '} + {Math.ceil((correctAnswers / total) * 100)}% + + + Udzielono poprawnej odpowiedzi na {correctAnswers}{' '} + {polishPlurals('pytanie', 'pytania', 'pytań', correctAnswers)} z{' '} + {total}. + +
+ ); +}; + +export default Summary; diff --git a/src/features/QualificationPage/features/TestPage/components/Test/Test.tsx b/src/features/QualificationPage/features/TestPage/components/Test/Test.tsx index fbb8a25..8ae0bf5 100644 --- a/src/features/QualificationPage/features/TestPage/components/Test/Test.tsx +++ b/src/features/QualificationPage/features/TestPage/components/Test/Test.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Answer, Qualification, Question as QuestionT } from 'libs/graphql'; import { @@ -13,6 +13,8 @@ import { import Section from 'common/Section/Section'; import TabPanel from './TabPanel'; import Question from './Question'; +import Navigation from './Navigation'; +import Summary from './Summary'; export interface TestProps { initialQuestions: QuestionT[]; @@ -20,19 +22,47 @@ export interface TestProps { } const Test = ({ initialQuestions, qualification }: TestProps) => { + const headingRef = useRef(null); const [questions, setQuestions] = useState(initialQuestions); const [selectedAnswers, setSelectedAnswers] = useState( 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()); + + useEffect(() => { + if (headingRef.current?.scrollIntoView) { + headingRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + } + }, [currentTab]); + + const handleReset = () => { + setStartedAt(new Date()); + setEndedAt(new Date()); + setSelectedAnswers(new Array(initialQuestions.length).fill('')); + setCurrentTab(0); + setReviewMode(false); + }; + + const handleFinish = () => { + setEndedAt(new Date()); + setCurrentTab(currentTab => currentTab + 1); + setReviewMode(true); + }; return (
- - Kwalifikacja {qualification.code} - +
+ + Kwalifikacja {qualification.code} + +
{questions.length === 0 ? ( Do tej kwalifikacji nie zostały dodane żadne pytania. @@ -57,7 +87,7 @@ const Test = ({ initialQuestions, qualification }: TestProps) => { {questions.map((question, index) => ( - + { /> ))} + + + + setCurrentTab(currentTab - 1)} + onRequestNextTab={() => setCurrentTab(currentTab + 1)} + isLastQuestion={ + currentTab + 1 === questions.length + (reviewMode ? 1 : 0) + } + reviewMode={reviewMode} + onReset={handleReset} + onFinish={handleFinish} + /> )} diff --git a/src/features/QualificationPage/features/TestPage/queries.ts b/src/features/QualificationPage/features/TestPage/queries.ts index 2e77a47..c0d0acf 100644 --- a/src/features/QualificationPage/features/TestPage/queries.ts +++ b/src/features/QualificationPage/features/TestPage/queries.ts @@ -20,6 +20,7 @@ export const QUERY_GENERATE_TEST_SIMILAR_QUALIFICATIONS = gql` ) { generateTest(limit: $limitTest, qualificationIDs: [$qualificationID]) { id + from content image answerA diff --git a/src/libs/hooks/useCountdown.ts b/src/libs/hooks/useCountdown.ts index 16ae5cd..a976174 100644 --- a/src/libs/hooks/useCountdown.ts +++ b/src/libs/hooks/useCountdown.ts @@ -7,7 +7,7 @@ export type DifferenceBetweenDates = { seconds: number; }; -const calculateDifference = ( +export const calculateDifferenceBetweenTwoDates = ( dateLeft: Date, dateRight: Date ): DifferenceBetweenDates => { @@ -23,12 +23,12 @@ const calculateDifference = ( export const useCountdown = (dateRight: Date): DifferenceBetweenDates => { const [difference, setDifference] = useState( - calculateDifference(new Date(), dateRight) + calculateDifferenceBetweenTwoDates(new Date(), dateRight) ); useEffect(() => { const timeout = setTimeout(() => { - setDifference(calculateDifference(new Date(), dateRight)); + setDifference(calculateDifferenceBetweenTwoDates(new Date(), dateRight)); }, 1000); return () => { clearTimeout(timeout);