| Автор | SHA1 | Сообщение | Дата |
|---|---|---|---|
|
|
42e7c66b1e | installed CircularProgressbar libary and recreated the quiz countdown timer | 3 лет назад |
|
|
7f48003ee3 | fixed questions timer timelimit | 3 лет назад |
|
|
df87e13031 | calculated quiz logic and added the quiz results in results page | 3 лет назад |
|
|
1a66dd07e8 | created multiSelect and text input for quiz | 3 лет назад |
|
|
3e65733617 | fixed quiz timer update issue | 3 лет назад |
| @@ -29,6 +29,7 @@ | |||
| "node": "^17.7.2", | |||
| "node-sass": "^7.0.1", | |||
| "react": "^17.0.1", | |||
| "react-circular-progressbar": "^2.0.4", | |||
| "react-countdown-circle-timer": "^3.0.9", | |||
| "react-dom": "^17.0.1", | |||
| "react-router": "^5.2.0", | |||
| @@ -13121,6 +13122,14 @@ | |||
| "node": ">=14" | |||
| } | |||
| }, | |||
| "node_modules/react-circular-progressbar": { | |||
| "version": "2.0.4", | |||
| "resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.4.tgz", | |||
| "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==", | |||
| "peerDependencies": { | |||
| "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" | |||
| } | |||
| }, | |||
| "node_modules/react-countdown-circle-timer": { | |||
| "version": "3.0.9", | |||
| "resolved": "https://registry.npmjs.org/react-countdown-circle-timer/-/react-countdown-circle-timer-3.0.9.tgz", | |||
| @@ -26126,6 +26135,12 @@ | |||
| "whatwg-fetch": "^3.6.2" | |||
| } | |||
| }, | |||
| "react-circular-progressbar": { | |||
| "version": "2.0.4", | |||
| "resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.4.tgz", | |||
| "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==", | |||
| "requires": {} | |||
| }, | |||
| "react-countdown-circle-timer": { | |||
| "version": "3.0.9", | |||
| "resolved": "https://registry.npmjs.org/react-countdown-circle-timer/-/react-countdown-circle-timer-3.0.9.tgz", | |||
| @@ -24,6 +24,7 @@ | |||
| "node": "^17.7.2", | |||
| "node-sass": "^7.0.1", | |||
| "react": "^17.0.1", | |||
| "react-circular-progressbar": "^2.0.4", | |||
| "react-countdown-circle-timer": "^3.0.9", | |||
| "react-dom": "^17.0.1", | |||
| "react-router": "^5.2.0", | |||
| @@ -1,6 +1,6 @@ | |||
| import { QuizDetails } from "../models/QuizDetails"; | |||
| const Quiz_Details: QuizDetails[] = [ | |||
| const QUIZ_DETAILS: QuizDetails[] = [ | |||
| { | |||
| question: "How would you correctly display, “Hello, how are you?”?", | |||
| options: [ | |||
| @@ -11,7 +11,8 @@ const Quiz_Details: QuizDetails[] = [ | |||
| ], | |||
| answer: ["System.out.println('Hello, how are you?');"], | |||
| result: false, | |||
| timeLimit: 10 | |||
| timeLimit: 5, | |||
| type: "singleSelect" | |||
| }, | |||
| { | |||
| question: "How do you write 'Hello World' in an alert box?", | |||
| @@ -23,7 +24,9 @@ const Quiz_Details: QuizDetails[] = [ | |||
| ], | |||
| answer: ["alert('Hello World');"], | |||
| result: false, | |||
| timeLimit: 25 | |||
| timeLimit: 10, | |||
| type: "singleSelect" | |||
| }, | |||
| { | |||
| question: "is javascript", | |||
| @@ -35,16 +38,39 @@ const Quiz_Details: QuizDetails[] = [ | |||
| ], | |||
| answer: ["B", "C"], | |||
| result: false, | |||
| timeLimit: 35 | |||
| timeLimit: 15, | |||
| type: "multiSelect" | |||
| }, | |||
| { | |||
| question: "Is javascript single threaded or multi threaded? enter the answer in the below box ", | |||
| options: [], | |||
| answer: ["single threaded"], | |||
| result: false, | |||
| timeLimit: 40 | |||
| } | |||
| timeLimit: 40, | |||
| type: "textInput" | |||
| }, | |||
| { | |||
| question: "is javascript", | |||
| options: [ | |||
| "A", | |||
| "B", | |||
| "C", | |||
| "D" | |||
| ], | |||
| answer: ["B", "C"], | |||
| result: false, | |||
| timeLimit: 35, | |||
| type: "multiSelect" | |||
| }, | |||
| ]; | |||
| export let test = [ | |||
| { | |||
| a: "ss" | |||
| }, | |||
| { | |||
| a: "aa" | |||
| }, | |||
| ] | |||
| export default Quiz_Details; | |||
| export default QUIZ_DETAILS; | |||
| @@ -4,4 +4,5 @@ export interface QuizDetails { | |||
| answer: string[]; | |||
| result: boolean; | |||
| timeLimit: number; | |||
| type: string; | |||
| } | |||
| @@ -51,7 +51,7 @@ const StepsDescription: React.FC<Props> = (props) => { | |||
| {props.isRoundCompleted ? | |||
| <Link to="/interviewRounds" className={styles.button}> | |||
| <IonButton shape="round" expand='block'>Completed</IonButton> | |||
| <IonButton shape="round" expand='block' disabled={true}>Completed</IonButton> | |||
| </Link> | |||
| : | |||
| <Link to={props.link} className={styles.button}> | |||
| @@ -7,6 +7,8 @@ import goodJobIcon from "../../assets/icons/good_job.svg"; | |||
| import { closeCircle, checkmarkCircle } from "ionicons/icons"; | |||
| import { Link } from "react-router-dom"; | |||
| import Quiz_Details from '../../mockData/QuizDetails'; | |||
| const PreliminaryRoundResults: React.FC = () => { | |||
| return ( | |||
| @@ -18,7 +20,7 @@ const PreliminaryRoundResults: React.FC = () => { | |||
| <IonIcon src={goodJobIcon} /> | |||
| <div className={styles.score}> | |||
| <h5>You have scored</h5> | |||
| <h5 className={styles.result}>85%</h5> | |||
| <h5 className={styles.result}>{(parseInt(localStorage.getItem("answer")!) / Quiz_Details.length) * 100}%</h5> | |||
| </div> | |||
| <p className={styles.description}> | |||
| Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt. | |||
| @@ -29,11 +31,11 @@ const PreliminaryRoundResults: React.FC = () => { | |||
| <div className={styles.resultsHolder}> | |||
| <div className={styles.correct}> | |||
| <IonIcon icon={checkmarkCircle} /> | |||
| <div>17 Questions</div> | |||
| <div>{localStorage.getItem("answer")} Questions</div> | |||
| </div> | |||
| <div className={styles.wrong}> | |||
| <IonIcon icon={closeCircle} /> | |||
| <div>3 Questions</div> | |||
| <div>{Quiz_Details.length - parseInt(localStorage.getItem("answer")!)} Questions</div> | |||
| </div> | |||
| </div> | |||
| <div className={styles.buttonHolder}> | |||
| @@ -1,12 +1,75 @@ | |||
| .optionHolder { | |||
| ion-list { | |||
| ion-radio-group { | |||
| ion-item { | |||
| width: 99%; | |||
| margin: 0 auto; | |||
| --background: white; | |||
| border: 1px solid #DBDBDB; | |||
| border-radius: 25px; | |||
| ion-label { | |||
| --color: #626262; | |||
| font-size: 1.2rem !important; | |||
| font-weight: 200; | |||
| } | |||
| ion-radio { | |||
| --color-checked: var(--primary-button-color); | |||
| margin-left: 1.5rem; | |||
| } | |||
| } | |||
| .highlighted { | |||
| box-shadow: 0px 0px 10px #00000029; | |||
| ion-radio { | |||
| --color: var(--primary-button-color); | |||
| } | |||
| } | |||
| // styles for multiselect checkbox | |||
| .checkBoxHolder { | |||
| border: 0.2rem solid #707070; | |||
| border-radius: 2.5rem; | |||
| width: 2rem; | |||
| height: 2rem; | |||
| margin-left: 2rem; | |||
| .checkBox { | |||
| --background-checked: var(--primary-button-color); | |||
| --checkmark-color: var(--primary-button-color); | |||
| --border-width: 0; | |||
| margin: 0; | |||
| margin-top: 0.26rem; | |||
| margin-left: 0.27rem; | |||
| width: 1.2rem; | |||
| height: 1.2rem; | |||
| } | |||
| } | |||
| .multiSelectLabel { | |||
| margin-left: 2rem; | |||
| } | |||
| .options { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: space-around; | |||
| height: 40vh; | |||
| .textInput { | |||
| width: 95%; | |||
| margin: 0 auto; | |||
| width: 30rem; | |||
| height: 4rem; | |||
| font-size: 1.4rem; | |||
| border-radius: 2.5rem; | |||
| padding-left: 2rem; | |||
| } | |||
| ion-item { | |||
| width: 99%; | |||
| margin: 0 auto; | |||
| @@ -1,17 +1,67 @@ | |||
| import { IonButton, IonItem, IonLabel, IonList, IonRadio, IonRadioGroup } from '@ionic/react'; | |||
| import { useState } from 'react'; | |||
| import { IonButton, IonCheckbox, IonItem, IonLabel, IonList, IonRadio, IonRadioGroup } from '@ionic/react'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { Link } from 'react-router-dom'; | |||
| import styles from './Options.module.scss'; | |||
| interface OwnProps { | |||
| options: string[] | undefined; | |||
| type: string; | |||
| answer: string[]; | |||
| lastQuestion: boolean; | |||
| questionNumber: number; | |||
| updateQuestionNumber: () => void; | |||
| } | |||
| const Options: React.FC<OwnProps> = (props) => { | |||
| const [selected, setSelected] = useState<string | undefined>(undefined); | |||
| const [selectedOptions, setSelectedOptions] = useState<string[]>([]); | |||
| const [textInput, setTextInput] = useState<string>(""); | |||
| const [answers, setAnswers] = useState<number>(0); | |||
| const inputRef = useRef<HTMLInputElement>(null); | |||
| useEffect(() => { | |||
| setSelected(undefined); | |||
| setSelectedOptions([]); | |||
| setTextInput(""); | |||
| }, [props.options]); | |||
| const selectChecked = (option: string) => { | |||
| let newOption: string[] = []; | |||
| if (selectedOptions.includes(option)) { | |||
| newOption = selectedOptions.filter(oldOption => oldOption !== option); | |||
| } else { | |||
| newOption = selectedOptions.concat([option]); | |||
| } | |||
| setSelectedOptions(newOption); | |||
| } | |||
| const validateAnswer = () => { | |||
| if (props.type === "singleSelect") { | |||
| if (props.answer.includes(selected!)) { | |||
| setAnswers(answers + 1); | |||
| } | |||
| } else if (props.type === "multiSelect") { | |||
| if (props.answer.length === selectedOptions.length && props.answer.sort().join(',') === selectedOptions.sort().join(',')) { | |||
| setAnswers(answers + 1); | |||
| } | |||
| } else if (props.type === "textInput") { | |||
| if (props.answer.includes(textInput)) { | |||
| setAnswers(answers + 1); | |||
| } | |||
| } | |||
| } | |||
| const handleInput = () => { | |||
| setTextInput(inputRef.current?.value!) | |||
| } | |||
| if (props.lastQuestion) { | |||
| localStorage.setItem("answer", answers.toString()); | |||
| } | |||
| // console.log(selected); | |||
| const options = props.options!.map((option, key) => { | |||
| return ( | |||
| @@ -22,19 +72,55 @@ const Options: React.FC<OwnProps> = (props) => { | |||
| ); | |||
| }); | |||
| const MultiSelectOptions = props.options!.map((option, key) => { | |||
| return ( | |||
| <IonItem lines='none' key={key} className={(selectedOptions.includes(option)) ? styles.highlighted : ""}> | |||
| <div className={styles.checkBoxHolder}> | |||
| <IonCheckbox slot="start" mode='ios' className={styles.checkBox} onIonChange={e => selectChecked(option)} /> | |||
| </div> | |||
| <IonLabel className={styles.multiSelectLabel}>{option}</IonLabel> | |||
| </IonItem> | |||
| ); | |||
| }); | |||
| return ( | |||
| <div className={styles.optionHolder}> | |||
| <IonList> | |||
| <IonRadioGroup onIonChange={e => setSelected(e.detail.value)}> | |||
| {options} | |||
| </IonRadioGroup> | |||
| {props.type === "singleSelect" && | |||
| <IonRadioGroup onIonChange={e => setSelected(e.detail.value)} allowEmptySelection={true} className={styles.options}> | |||
| {options} | |||
| </IonRadioGroup> | |||
| } | |||
| {props.type === "multiSelect" && | |||
| <div className={styles.options}> | |||
| {MultiSelectOptions} | |||
| </div> | |||
| } | |||
| {props.type === "textInput" && | |||
| <div className={styles.options}> | |||
| <input | |||
| type="text" | |||
| className={styles.textInput} | |||
| placeholder="enter your answer" | |||
| ref={inputRef} | |||
| onChange={handleInput} /> | |||
| </div> | |||
| } | |||
| </IonList> | |||
| <div className={styles.button + " " + (selected ? styles.active : "")} | |||
| onClick={() => props.updateQuestionNumber()}> | |||
| <IonButton shape="round" expand='block'>Next</IonButton> | |||
| </div> | |||
| </div> | |||
| {!props.lastQuestion && | |||
| <div className={styles.button + " " + ((selected || selectedOptions.length > 0 || textInput) ? styles.active : "")} | |||
| onClick={(selected || selectedOptions.length > 0 || textInput) ? () => { props.updateQuestionNumber(); validateAnswer(); } : undefined}> | |||
| <IonButton shape="round" expand='block'>Next</IonButton> | |||
| </div> | |||
| } | |||
| {props.lastQuestion && | |||
| < Link to="/preliminaryRoundResults" className={styles.button + " " + ((selected || selectedOptions.length > 0 || textInput) ? styles.active : "")}> | |||
| <IonButton shape="round" expand='block'>Next Step</IonButton> | |||
| </Link> | |||
| } | |||
| </div > | |||
| ); | |||
| } | |||
| @@ -28,6 +28,10 @@ section { | |||
| .time { | |||
| font-size: 1.4rem; | |||
| } | |||
| .progressBar{ | |||
| width: 5.5rem; | |||
| height: 5.5rem; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,7 +1,8 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import styles from "./Question.module.scss"; | |||
| import { CountdownCircleTimer } from 'react-countdown-circle-timer'; | |||
| import { secondsToMinutes } from "date-fns"; | |||
| import { CircularProgressbar, buildStyles } from 'react-circular-progressbar'; | |||
| import 'react-circular-progressbar/dist/styles.css'; | |||
| interface OwnProp { | |||
| questionNumber: number; | |||
| @@ -10,37 +11,34 @@ interface OwnProp { | |||
| updateQuestionNumber: () => void; | |||
| } | |||
| let timeout: NodeJS.Timeout; | |||
| const Question: React.FC<OwnProp> = (props) => { | |||
| const [seconds, setSeconds] = useState<number>(props.timeLimit); | |||
| const [duration, setDuration] = useState<number>(props.timeLimit); | |||
| const displaySeconds = seconds % 60; | |||
| const percentage = Math.round(seconds / props.timeLimit * 100); | |||
| const time = `${secondsToMinutes(seconds).toString().padStart(2, '0')}:${displaySeconds.toString().padStart(2, '0')}` | |||
| useEffect(() => { | |||
| setTimeout(() => { | |||
| if (seconds <= 0) { | |||
| props.updateQuestionNumber(); | |||
| console.log(props.timeLimit); | |||
| setSeconds(5) | |||
| } else { | |||
| setSeconds(props.timeLimit); | |||
| setDuration(props.timeLimit); | |||
| }, [props.questionNumber]); | |||
| useEffect(() => { | |||
| const timeOut = setTimeout(() => { | |||
| if (seconds > 0) { | |||
| setSeconds(seconds - 1); | |||
| } else { | |||
| props.updateQuestionNumber(); | |||
| } | |||
| }, 500); | |||
| }, [seconds]); | |||
| }, 1000); | |||
| return () => clearInterval(timeOut); | |||
| }, [seconds]); | |||
| const renderTime = () => { | |||
| return ( | |||
| <div className={styles.time}> | |||
| { | |||
| `${secondsToMinutes(seconds).toString().padStart(2, '0')}: | |||
| ${displaySeconds.toString().padStart(2, '0')}` | |||
| } | |||
| </div> | |||
| ); | |||
| } | |||
| return ( | |||
| <section> | |||
| @@ -53,22 +51,18 @@ const Question: React.FC<OwnProp> = (props) => { | |||
| </div> | |||
| <div className={styles.quizTimer}> | |||
| <CountdownCircleTimer | |||
| isPlaying | |||
| duration={props.timeLimit} | |||
| colors={'#6BD534'} | |||
| size={60} | |||
| onComplete={() => { | |||
| // setSeconds(COUNTDOWN_AMOUNT_TOTAL); | |||
| // props.updateQuestionNumber(); | |||
| return { shouldRepeat: true } | |||
| }} | |||
| strokeWidth={5} > | |||
| {renderTime} | |||
| </CountdownCircleTimer> | |||
| <div className={styles.progressBar}> | |||
| <CircularProgressbar | |||
| value={percentage} | |||
| text={`${time}`} | |||
| counterClockwise={true} | |||
| styles={buildStyles({ | |||
| trailColor: '#ffff', | |||
| pathColor: '#6BD534', | |||
| textColor: "#ffff", | |||
| textSize: "2.4rem" | |||
| })} />; | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| @@ -4,22 +4,20 @@ import { IonButton, IonIcon } from '@ionic/react'; | |||
| import Options from './Options'; | |||
| import { closeOutline } from 'ionicons/icons' | |||
| import { Link } from 'react-router-dom'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { useState } from 'react'; | |||
| import Question from './Question'; | |||
| import Quiz_Details from '../../mockData/QuizDetails'; | |||
| import QUIZ_DETAILS from '../../mockData/QuizDetails'; | |||
| const Quiz: React.FC = () => { | |||
| const [questionNumber, setQuestionNumber] = useState<number>(1); | |||
| const timeLimit = QUIZ_DETAILS[questionNumber - 1].timeLimit; | |||
| const updateQuestionNumber = () => { | |||
| console.log("update") | |||
| if (Quiz_Details.length > questionNumber) { | |||
| if (QUIZ_DETAILS.length > questionNumber) { | |||
| setQuestionNumber((questionNumber) => questionNumber + 1); | |||
| } | |||
| } | |||
| console.log("no", questionNumber); | |||
| console.log(Quiz_Details[questionNumber - 1]); | |||
| return ( | |||
| <div className={styles.quizContainer}> | |||
| @@ -31,22 +29,25 @@ const Quiz: React.FC = () => { | |||
| </Link> | |||
| </header> | |||
| <Question question={Quiz_Details[questionNumber - 1].question} | |||
| <Question | |||
| question={QUIZ_DETAILS[questionNumber - 1].question} | |||
| questionNumber={questionNumber} | |||
| timeLimit={Quiz_Details[questionNumber - 1].timeLimit} | |||
| timeLimit={timeLimit} | |||
| updateQuestionNumber={updateQuestionNumber} /> | |||
| </div> | |||
| <div className={styles.quizOptions}> | |||
| <div className={styles.options}> | |||
| <Options options={Quiz_Details[questionNumber - 1].options} updateQuestionNumber={updateQuestionNumber} /> | |||
| <Options | |||
| options={QUIZ_DETAILS[questionNumber - 1].options} | |||
| updateQuestionNumber={updateQuestionNumber} | |||
| type={QUIZ_DETAILS[questionNumber - 1].type} | |||
| answer={QUIZ_DETAILS[questionNumber - 1].answer} | |||
| lastQuestion={QUIZ_DETAILS.length === questionNumber} | |||
| questionNumber={questionNumber} /> | |||
| </div> | |||
| {/* <Link to="/" className={styles.button + " " + (selected ? styles.active : "")}> | |||
| <IonButton shape="round" expand='block'>Next</IonButton> | |||
| </Link> */} | |||
| </div> | |||
| </div> | |||
| @@ -15,7 +15,7 @@ const TechnicalInterview: React.FC = () => { | |||
| const [isDateSet, setDate] = useState<boolean>(false); | |||
| const [isTimeSlot, setTimeSlot] = useState<boolean>(false); | |||
| const [days, hours, minutes, seconds] = useCountdown(new Date('may 6, 2022 07:00:00')); | |||
| const [days, hours, minutes, seconds] = useCountdown(new Date('may 7, 2022 07:00:00')); | |||
| const getDate = () => { | |||
| setTimeSlot(false); | |||