diff --git a/src/lib/repositories/scans/fetchScanQuestionRange.ts b/src/lib/repositories/scans/fetchScanQuestionRange.ts new file mode 100644 index 00000000..61177c87 --- /dev/null +++ b/src/lib/repositories/scans/fetchScanQuestionRange.ts @@ -0,0 +1,21 @@ +import { fetchJsonData } from '../../fetcher/DataFetcher'; +import { getEnvValue } from '../../utility/env/env'; +import { EnvOptions } from '../../utility/env/env.values'; +import { stringToBase64 } from '../../utility/stringutils'; + +export default function fetchScanQuestionRange(ids: string[]) { + const username = getEnvValue(EnvOptions.WordPressUsername); + const password = getEnvValue(EnvOptions.WordPressPassword); + + return fetchJsonData({ + input: + getEnvValue(EnvOptions.WordPressDataURL) + + 'wp-json/wins/v1/app/questions/' + + ids.join(','), + init: { + headers: { + Authorization: `Basic ${stringToBase64(`${username}:${password}`)}`, + }, + }, + }); +} diff --git a/src/lib/repositories/scans/useScanQuestionRange.ts b/src/lib/repositories/scans/useScanQuestionRange.ts new file mode 100644 index 00000000..2b5f95f8 --- /dev/null +++ b/src/lib/repositories/scans/useScanQuestionRange.ts @@ -0,0 +1,15 @@ +import { ScanQuestion } from '../../../types/Scan'; +import { useDataFetcher, fetchJsonData } from '../../fetcher/DataFetcher'; +import { getEnvValue } from '../../utility/env/env'; +import { EnvOptions } from '../../utility/env/env.values'; + +export default function useScanQuestionRange(ids: string[]) { + return useDataFetcher(fetchJsonData, { + url: + getEnvValue(EnvOptions.WordPressDataURL) + + 'wp-json/wins/v1/app/questions/' + + ids.join(','), + username: getEnvValue(EnvOptions.WordPressUsername), + password: getEnvValue(EnvOptions.WordPressPassword), + }); +} diff --git a/src/lib/repositories/scans/useSingleScan.ts b/src/lib/repositories/scans/useSingleScan.ts new file mode 100644 index 00000000..460d5181 --- /dev/null +++ b/src/lib/repositories/scans/useSingleScan.ts @@ -0,0 +1,15 @@ +import { Scan } from '../../../types/Scan'; +import { useDataFetcher, fetchJsonData } from '../../fetcher/DataFetcher'; +import { getEnvValue } from '../../utility/env/env'; +import { EnvOptions } from '../../utility/env/env.values'; + +export default function useSingleScan(id: string | undefined) { + return useDataFetcher(fetchJsonData, { + url: + getEnvValue(EnvOptions.WordPressDataURL) + + 'wp-json/wins/v1/app/scans/' + + id, + username: getEnvValue(EnvOptions.WordPressUsername), + password: getEnvValue(EnvOptions.WordPressPassword), + }); +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 6fb5fe3b..19f36b3a 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -14,6 +14,7 @@ import { PodcastsEpisodePage } from '../screens/Podcasts/PodcastsEpisodePage'; import { PromptLibrary } from '../screens/PromptLibrary/PromptLibrary'; import { PromptView } from '../screens/PromptLibrary/PromptView'; import { Quizzes } from '../screens/Quizzes'; +import { QuestionPage } from '../screens/Scans/QuestionPage'; import { SettingsScreen } from '../screens/Settings'; import { StudyScreen } from '../screens/Study'; import { CaseStudyInfo } from '../screens/Usecase/CaseStudyInfo'; @@ -23,6 +24,7 @@ import { BackgroundInfo } from '../screens/UserBackground/BackgroundInfo'; import { WTRScreen } from '../screens/WTR'; import { MockTutorial } from '../screens/WTR/MockTutorial'; import { WTRContentScreen } from '../screens/WTR/WTRContent'; + const Stack = createNativeStackNavigator(); const screens = [ @@ -46,6 +48,7 @@ const screens = [ { name: Routes.Quizzes, component: Quizzes }, { name: Routes.MockTutorial, component: MockTutorial }, { name: Routes.PodcastsEpisodePage, component: PodcastsEpisodePage }, + { name: Routes.QuestionPage, component: QuestionPage }, ]; export const Router = () => { diff --git a/src/routes/navigation.ts b/src/routes/navigation.ts index 681d00a1..e3ddad0b 100644 --- a/src/routes/navigation.ts +++ b/src/routes/navigation.ts @@ -10,5 +10,6 @@ export const navigationBarLinks: NavigationBarLink[] = [ { icon: 'articles', route: Routes.Articles }, { icon: 'quizzes', route: Routes.Quizzes }, { icon: 'prompts', route: Routes.PromptLibrary }, - { icon: 'WTR', route: Routes.WindesheimTechRadar }, + { icon: 'questions', route: Routes.QuestionPage }, + // { icon: 'WTR', route: Routes.WindesheimTechRadar }, ]; diff --git a/src/routes/routeLinking.ts b/src/routes/routeLinking.ts index 79335d62..673a669a 100644 --- a/src/routes/routeLinking.ts +++ b/src/routes/routeLinking.ts @@ -47,6 +47,12 @@ export const RouteLinking = { [Routes.Quizzes]: 'quizzes', [Routes.Articles]: 'articles', [Routes.PodcastsEpisodePage]: 'podcasts/episode', + [Routes.QuestionPage]: { + path: 'scans/:scanId/questions', + parse: { + scanId: (scanId: string) => scanId, + }, + }, }, }, }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 2a12c6f5..3c9f12f7 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -20,6 +20,7 @@ export enum Routes { Prompts = 'Prompts', MockTutorial = 'MockTutorial', PodcastsEpisodePage = 'PodcastsEpisodePage', + QuestionPage = 'QuestionPage', } export const DefaultRoute = Routes.Home; diff --git a/src/screens/Scans/QuestionPage.tsx b/src/screens/Scans/QuestionPage.tsx new file mode 100644 index 00000000..62f00396 --- /dev/null +++ b/src/screens/Scans/QuestionPage.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { Button, ProgressBar } from 'react-native-paper'; + +import fetchScanQuestionRange from '../../lib/repositories/scans/fetchScanQuestionRange'; +import useScanQuestionRange from '../../lib/repositories/scans/useScanQuestionRange'; +import useSingleScan from '../../lib/repositories/scans/useSingleScan'; +import { ScanQuestion } from '../../types/Scan'; + +export function QuestionPage() { + const [currentQuestionIndex, setCurrentQuestionIndex] = + React.useState(0); + const [currentQuestion, setCurrentQuestion] = React.useState< + ScanQuestion | undefined + >(undefined); + const [questions, setQuestions] = React.useState([]); + + const [questionCache, setQuestionCache] = React.useState( + [], + ); + + const scanId = '1'; + + const { + data: scanData, + // isLoading: scanIsLoading, + // error: scanError, + } = useSingleScan(scanId); + + const parsedQuestions: ScanQuestion[] = []; + + if (scanData && parsedQuestions.length === 0) { + scanData.categories + .sort((a, b) => parseInt(a.idx, 10) - parseInt(b.idx, 10)) + .forEach((category) => { + category.questions + .sort((a, b) => parseInt(a.idx, 10) - parseInt(b.idx, 10)) + .forEach((question) => { + parsedQuestions.push(question); + }); + }); + } + + const fetchQuestions = (currentId: string) => { + const currentIndex = parsedQuestions.findIndex( + (q) => q.id === currentId, + ); + + const indexes = [ + currentIndex - 1, + currentIndex, + currentIndex + 1, + currentIndex + 2, + currentIndex + 3, + currentIndex + 4, + ]; + + const ids: string[] = []; + + indexes.forEach((index) => { + if (parsedQuestions.length === 0) { + if (index >= 0) { + ids.push(index.toString()); + } + return; + } + + const question = parsedQuestions.find( + (q) => q.id === index.toString(), + ); + + if (question && !questionCache.find((q) => q.id === question.id)) { + ids.push(question.id); + } + }); + + if (ids.length === 0) return; + + fetchScanQuestionRange(ids) + .then((data: ScanQuestion[]) => { + if (!data) return; + + const newQuestionCache = [...questionCache]; + data.forEach((q) => { + if (!newQuestionCache.find((qc) => qc.id === q.id)) { + newQuestionCache.push(q); + } + }); + setQuestionCache(newQuestionCache); + + if (currentQuestion === undefined) { + setCurrentQuestion(data.find((q) => q.id === currentId)); + } + }) + .catch((error) => { + throw error; + }); + }; + + const firstId = parsedQuestions[0]?.id; + const { + data: firstQuestionData, + // isLoading: firstQuestionIsLoading, + // error: firstQuestionError, + } = useScanQuestionRange([firstId]); + + if (firstQuestionData && questionCache.length === 0) { + setQuestionCache(firstQuestionData); + setCurrentQuestion(firstQuestionData[0]); + fetchQuestions(firstId); + } + + if (parsedQuestions.length > 0 && questions.length === 0) { + setQuestions(parsedQuestions); + setCurrentQuestionIndex(parseInt(parsedQuestions[0].id, 10)); + } + + const showNextQuestion = () => { + const nextIndex = currentQuestionIndex + 1; + showQuestion(nextIndex); + }; + + const showPreviousQuestion = () => { + const previousIndex = currentQuestionIndex - 1; + showQuestion(previousIndex); + }; + + const showQuestion = (index: number) => { + const maxIndex = parsedQuestions.length; + + if (index < 1) { + index = 1; + } + + if (index > maxIndex) { + index = maxIndex; + } + + setCurrentQuestionIndex(index); + setCurrentQuestion( + questionCache.find((q) => q.id === index.toString()), + ); + + if (!parsedQuestions[index]) return; + + try { + fetchQuestions(parsedQuestions[index].id); + } catch (error) { + console.log(error); + fetchQuestions(parsedQuestions[index].id); + } + }; + + const getProgress = () => { + const maxIndex = parsedQuestions.length; + const currentIndex = currentQuestionIndex; + + return currentIndex / maxIndex; + }; + + return ( + + + Question: {currentQuestion?.text} + Description: {currentQuestion?.description} + + + + ); +} diff --git a/src/types/Scan.ts b/src/types/Scan.ts new file mode 100644 index 00000000..17372433 --- /dev/null +++ b/src/types/Scan.ts @@ -0,0 +1,26 @@ +export type Scan = { + id: string; + name: string; + description: string; + content: string; + scanType: string; + difficulty: string; + imageUrl: any; + categories: Category[]; +}; + +type Category = { + id: string; + idx: string; + name: string; + description: string; + questions: ScanQuestion[]; +}; + +export type ScanQuestion = { + id: string; + idx: string; + categoryId?: string; + text?: string; + description?: string; +};