diff --git a/002_source/ionic_mobile/src/RouteConfig.tsx b/002_source/ionic_mobile/src/RouteConfig.tsx index eda18ef..bd1a19d 100644 --- a/002_source/ionic_mobile/src/RouteConfig.tsx +++ b/002_source/ionic_mobile/src/RouteConfig.tsx @@ -100,6 +100,7 @@ function RouteConfig() { {/* */} + {/* http://localhost:5173/matching_frenzy/r/000000000000001 */} diff --git a/002_source/ionic_mobile/src/contexts/AppState.tsx b/002_source/ionic_mobile/src/contexts/AppState.tsx index c0c089a..03e399f 100644 --- a/002_source/ionic_mobile/src/contexts/AppState.tsx +++ b/002_source/ionic_mobile/src/contexts/AppState.tsx @@ -30,7 +30,9 @@ export const AppStateProvider: React.FC<{ children: ReactNode }> = ({ children } // user_config.json // 012_put_matching_frenzy_count_down_time_to_config_file const [user_config_json, setUserConfigJson] = useState({}); - const [MATCHING_FRENZY_COUNT_DOWN_S, setMATCHING_FRENZY_COUNT_DOWN_S] = useState(120); + + // TODO: resume to 120 + const [MATCHING_FRENZY_COUNT_DOWN_S, setMATCHING_FRENZY_COUNT_DOWN_S] = useState(300); useEffect(() => { fetch('/data/user_config.json') .then((res) => res.json()) diff --git a/002_source/ionic_mobile/src/hooks/fetchMFQuestions.tsx b/002_source/ionic_mobile/src/hooks/fetchMFQuestions.tsx new file mode 100644 index 0000000..29f0507 --- /dev/null +++ b/002_source/ionic_mobile/src/hooks/fetchMFQuestions.tsx @@ -0,0 +1,28 @@ +import { QuizMFQuestion } from '../types/QuizMFQuestion'; +import { usePocketBase } from './usePocketBase'; +import { QueryClient } from '@tanstack/react-query'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: Infinity, + }, + }, +}); + +const fetchMFQuestions = async (cat_id: string, pb: any) => { + const response = await queryClient.fetchQuery({ + queryKey: ['fetchData'], + staleTime: 60 * 1000, + queryFn: async () => { + return await pb.collection('QuizMFQuestions').getList(1, 9999, { + filter: `cat_id = "${cat_id}"`, + $autoCancel: false, + }); + }, + }); + + return response; +}; + +export default fetchMFQuestions; diff --git a/002_source/ionic_mobile/src/hooks/useListQuizMFQuestionsByCategoryId.tsx b/002_source/ionic_mobile/src/hooks/useListQuizMFQuestionsByCategoryId.tsx new file mode 100644 index 0000000..093819b --- /dev/null +++ b/002_source/ionic_mobile/src/hooks/useListQuizMFQuestionsByCategoryId.tsx @@ -0,0 +1,44 @@ +import { QuizMFQuestion } from '../types/QuizMFQuestion'; +import { usePocketBase } from './usePocketBase'; +import { QueryClient } from '@tanstack/react-query'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: Infinity, + }, + }, +}); + +const useListQuizMFQuestionsByCategoryId = (cat_id: string) => { + const { user, pb } = usePocketBase(); + + const fetchData = async () => { + const response = await queryClient.fetchQuery({ + queryKey: ['fetchData'], + staleTime: 60 * 1000, + queryFn: async () => { + return await pb.collection('QuizMFQuestions').getList(1, 9999, { + filter: `cat_id = "${cat_id}"`, + $autoCancel: false, + }); + }, + }); + + return response; + }; + + return useQuery({ + queryKey: ['useListQuizMFQuestionsByCategoryId'], + staleTime: 60 * 1000, + queryFn: async () => { + return await pb.collection('QuizMFQuestions').getList(1, 9999, { + filter: `cat_id = "${cat_id}"`, + $autoCancel: false, + }); + }, + // enabled: !!user.id, + }); +}; + +export default useListQuizMFQuestionsByCategoryId; diff --git a/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDown.tsx b/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDown.tsx index be15faa..dfd8784 100644 --- a/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDown.tsx +++ b/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDown.tsx @@ -1,5 +1,4 @@ -import { useEffect } from 'react'; -// import Helloworld from './Helloworld'; +import React, { useEffect } from 'react'; import './style.css'; function CountDown({ diff --git a/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDownDisplay/index.tsx b/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDownDisplay/index.tsx new file mode 100644 index 0000000..0fa3a6a --- /dev/null +++ b/002_source/ionic_mobile/src/pages/MatchingFrenzy/CountDownDisplay/index.tsx @@ -0,0 +1,20 @@ +import React, { useState } from 'react'; +import { alarmOutline, arrowBackCircleOutline } from 'ionicons/icons'; +import { IonButton, IonContent, IonIcon, IonPage, useIonRouter } from '@ionic/react'; +import { useAppStateContext } from '../../../contexts/AppState'; + +function CountDownDisplay() { + const { MATCHING_FRENZY_COUNT_DOWN_S } = useAppStateContext(); + + const [countdown_s, setCountDown_s] = useState(MATCHING_FRENZY_COUNT_DOWN_S); + const [show_time_left_s, setShowTimeLeft] = useState('0:00'); + + return ( +
+ + {countdown_s > 0 ? show_time_left_s : 'times up'} +
+ ); +} + +export default CountDownDisplay; diff --git a/002_source/ionic_mobile/src/pages/MatchingFrenzy/MatchRun.tsx b/002_source/ionic_mobile/src/pages/MatchingFrenzy/MatchRun.tsx index 0c79662..b373931 100644 --- a/002_source/ionic_mobile/src/pages/MatchingFrenzy/MatchRun.tsx +++ b/002_source/ionic_mobile/src/pages/MatchingFrenzy/MatchRun.tsx @@ -3,47 +3,48 @@ import { alarmOutline, arrowBackCircleOutline } from 'ionicons/icons'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { LoadingScreen } from '../../components/LoadingScreen'; -import { MATCHING_FRENZY_LINK, QUIZ_MAIN_MENU_LINK } from '../../constants'; +import { MATCHING_FRENZY_LINK } from '../../constants'; import { useAppStateContext } from '../../contexts/AppState'; import { useMyIonQuizContext } from '../../contexts/MyIonQuiz'; import IMatchingFrenzyQuestion from '../../interfaces/IMatchingFrenzyQuestion'; -import { listMatchingFrenzyContent } from '../../public_data/listMatchingFrenzyContent'; import { shuffleArray } from '../../utils/shuffleArray'; import CountDown from './CountDown'; import MatchingFrenzyCard from './MatchingFrenzyCard'; import PressStartToBegin from './PressStartToBegin'; import './style.css'; import ConfirmUserQuitQuiz from '../../components/ConfirmUserQuitQuiz'; +import formatTimeDisplay from './formatTimeDisplay'; +import fetchMFQuestions from '../../hooks/fetchMFQuestions'; +import { usePocketBase } from '../../hooks/usePocketBase'; function MatchingFrenzyMatchRun() { const [loading, setLoading] = useState(true); - const router = useIonRouter(); const { p_route } = useParams<{ p_route: string }>(); - const i_p_route = parseInt(p_route); - - const [question_list, setQuestionList] = useState([]); + const { p_route: cat_id } = useParams<{ p_route: string }>(); + let [start_match, setStartMatch] = useState(false); + let [test_random, setTestRandom] = useState(0); + const [show_confirm_user_exit, setShowConfirmUserExit] = useState(false); + const { setMatchingFrenzyInProgress } = useAppStateContext(); + const [question_list, setQuestionList] = useState([]); const [current_question_meta, setCurrentQuestionMeta] = useState(undefined); - const [current_question, setCurrentQuestion] = useState(0); - const [num_correct, setNumCorrect] = useState(0); - - const { setTabActive } = useAppStateContext(); - - const [answer_list, setAnswerList] = useState([]); - const { MATCHING_FRENZY_COUNT_DOWN_S } = useAppStateContext(); const [countdown_s, setCountDown_s] = useState(MATCHING_FRENZY_COUNT_DOWN_S); const [show_time_left_s, setShowTimeLeft] = useState('0:00'); - const { setMatchingFrenzyInProgress } = useAppStateContext(); - const { setMatchingFrenzyCurrentTest, setMatchingFrenzyResult, saveMatchingFrenzyResultToScoreBoard } = - useMyIonQuizContext(); + const [num_correct, setNumCorrect] = useState(0); + const [answer_list, setAnswerList] = useState([]); - let [start_match, setStartMatch] = useState(false); - let [test_random, setTestRandom] = useState(0); + // TODO: review useless + const i_p_route = parseInt(p_route); - const formatTimeDisplay = (s: number) => { - return `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, '0')}`; + function processStartMatch() { + setMatchingFrenzyInProgress(true); + setStartMatch(true); + } + + const incNumCorrect = () => { + setNumCorrect(num_correct + 1); }; const nextQuestion = () => { @@ -71,15 +72,8 @@ function MatchingFrenzyMatchRun() { setCurrentQuestionMeta(question_list[next_question_num]); } }; - - const incNumCorrect = () => { - setNumCorrect(num_correct + 1); - }; - - function processStartMatch() { - setMatchingFrenzyInProgress(true); - setStartMatch(true); - } + const { setMatchingFrenzyCurrentTest, setMatchingFrenzyResult, saveMatchingFrenzyResultToScoreBoard } = + useMyIonQuizContext(); useEffect(() => { setShowTimeLeft(formatTimeDisplay(countdown_s)); @@ -102,9 +96,9 @@ function MatchingFrenzyMatchRun() { }, [countdown_s]); const [init_ans, setInitAns] = useState([]); - useEffect(() => { if (!current_question_meta) return; + let init_options = [...question_list.map((q) => q.word), ...answer_list, ...init_ans]; let all_answer_list = [...new Set(init_options)]; @@ -115,83 +109,73 @@ function MatchingFrenzyMatchRun() { setAnswerList(shuffleArray([...sliced_shuffle_array, current_question_meta.word])); }, [current_question_meta]); + const { user, pb } = usePocketBase(); + useEffect(() => { - (async () => { - const res_json = await listMatchingFrenzyContent(); - const cat_json = res_json[i_p_route]; - const init_answer = cat_json.init_answer; - setInitAns(cat_json.init_answer); + fetchMFQuestions(cat_id, pb) + .then((result) => { + console.log(result); + const cat_json = result.items; + const init_answer = result.items[0].init_answer; + setInitAns(init_answer); - let temp = res_json[i_p_route].content; - let shuffled_temp = shuffleArray(temp); + let temp = result.items; + let shuffled_temp = shuffleArray(temp); - setQuestionList(shuffled_temp); - setCurrentQuestionMeta(cat_json.content[current_question]); - })(); + setQuestionList(shuffled_temp); + setCurrentQuestionMeta(result.items[current_question] as unknown as IMatchingFrenzyQuestion); - setTabActive(QUIZ_MAIN_MENU_LINK); - setTestRandom(Math.random()); - }, []); - - const [show_confirm_user_exit, setShowConfirmUserExit] = useState(false); + setLoading(false); + }) + .catch((error) => { + console.error(error); + }); + }, [cat_id]); if (!current_question_meta) return ; + if (loading) return ; return ( <> {start_match ? ( - <> -
-
-
- setShowConfirmUserExit(true)}> - - -
-
- - {countdown_s > 0 ? show_time_left_s : 'times up'} -
-
- Matches:{num_correct} -
+
+
+
+ setShowConfirmUserExit(true)}> + +
-
- {/* */} - {/* -
-
- - {countdown_s > 0 ? show_time_left_s : 'times up'} -
-
Matches:{num_correct}
+
+ + {countdown_s > 0 ? show_time_left_s : 'times up'}
- */} - - +
Matches:{num_correct}
- +
+ + +
) : ( )} @@ -210,6 +194,7 @@ function MatchingFrenzyMatchRun() { } export default MatchingFrenzyMatchRun; + function isEndOfQuestionList(current_question: number, question_list: [] | IMatchingFrenzyQuestion[]) { return current_question >= question_list.length - 1; } diff --git a/002_source/ionic_mobile/src/pages/MatchingFrenzy/formatTimeDisplay.tsx b/002_source/ionic_mobile/src/pages/MatchingFrenzy/formatTimeDisplay.tsx new file mode 100644 index 0000000..68fe511 --- /dev/null +++ b/002_source/ionic_mobile/src/pages/MatchingFrenzy/formatTimeDisplay.tsx @@ -0,0 +1,5 @@ +const formatTimeDisplay = (s: number) => { + return `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, '0')}`; +}; + +export default formatTimeDisplay; diff --git a/002_source/ionic_mobile/src/types/QuizMFQuestion.ts b/002_source/ionic_mobile/src/types/QuizMFQuestion.ts new file mode 100644 index 0000000..7afb27b --- /dev/null +++ b/002_source/ionic_mobile/src/types/QuizMFQuestion.ts @@ -0,0 +1,19 @@ +export interface QuizMFQuestion { + id: string; + created: Date; + updated: Date; + word: string; + word_c: string; + cat_id: string; + visible?: string; + sound?: File; + cat_image?: File; + expand?: { + cat_id?: { + id: string; + cat_name: string; + cat_image?: File; + }; + }; + init_answer: string[]; +}