This commit is contained in:
louiscklaw
2025-04-29 16:09:30 +08:00
parent 4c72861eda
commit 9d3e832081
29 changed files with 446 additions and 136 deletions

View File

@@ -30,8 +30,8 @@ import WordPage from './pages/Lesson/WordPage';
import ListeningPractice from './pages/ListeningPractice';
import PracticeFinish from './pages/ListeningPractice/Finish';
import PracticeResult from './pages/ListeningPractice/Result';
import QuestionRoute from './pages/ListeningPractice/Route';
import CorrectionRoute from './pages/ListeningPractice/Route/CorrectionRoute';
import QuestionRoute from './pages/ListeningPractice/QuestionRoute';
import CorrectionRoute from './pages/ListeningPractice/QuestionRoute/CorrectionRoute';
import MatchingFrenzyMatchFinished from './pages/MatchingFrenzy/Finished';
import MatchingFrenzyMatchRun from './pages/MatchingFrenzy/MatchRun';
import MatchingFrenzyMatchResult from './pages/MatchingFrenzy/Result';
@@ -45,6 +45,7 @@ import Setting from './pages/Setting/indx';
import Tab1 from './pages/Tab1';
import Tab2 from './pages/Tab2';
import Tab3 from './pages/Tab3';
// import WordPageWithLayout from './pages/Lesson/WordPageWithLayout.del';
function RouteConfig() {
return (
@@ -81,16 +82,20 @@ function RouteConfig() {
<Route exact path={`${LISTENING_PRACTICE_LINK}/result`}>
<PracticeResult />
</Route>
{/* http://localhost:5173/listening_practice */}
<Route exact path={LISTENING_PRACTICE_LINK}>
<ListeningPractice />
</Route>
{/* */}
<Route exact path={`${CONNECTIVE_REVISION_LINK}/r/:p_route`}>
<ConnectiveRevisionQuizRun />
</Route>
<Route exact path={`${CONNECTIVE_REVISION_LINK}/finished`}>
<ConnectiveRevisionQuizResult />
</Route>
<Route exact path={CONNECTIVE_REVISION_LINK}>
<ConnectiveRevisionSelectCategory />
</Route>
@@ -104,6 +109,7 @@ function RouteConfig() {
<Route exact path={`${MATCHING_FRENZY_LINK}/result`}>
<MatchingFrenzyMatchResult />
</Route>
{/* http://localhost:5173/matching_frenzy/r/0 */}
<Route exact path={MATCHING_FRENZY_LINK}>
<MatchingFrenzySelectCategory />
</Route>
@@ -126,16 +132,23 @@ function RouteConfig() {
</Route>
{/* TODO: change lesson_idx to lesson_type_idx, need to modify LessonWordPage as well */}
{/* http://localhost:5173/lesson_word_page/v/000000000000001/000000000000003/0 */}
{/*
http://localhost:5173/lesson_word_page/v/000000000000001/000000000000003/0
layout: v
*/}
<Route exact path={`${LESSON_WORD_PAGE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
<WordPage />
</Route>
{/*
<Route exact path={`${LESSON_WORD_PAGE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
<LessonWordPage />
</Route>
*/}
{/*
http://localhost:5173/lesson_word_page/c/000000000000001/000000000000003/0
layout: c
*/}
<Route exact path={`${LESSON_WORD_PAGE_LINK}/c/:lesson_idx/:cat_idx/:word_idx`}>
<ConnectivesPage />
</Route>

View File

@@ -0,0 +1,3 @@
# GUIDELINE
`<base_dir>/002_source/ionic_mobile/src/RouteConfig.tsx` contains the route of the application.

View File

@@ -76,6 +76,7 @@ const TEST = process.env.NODE_ENV === 'test';
const MY_FAVORITE = 'My Favorite';
//
const POCKETBASE_URL = import.meta.env.VITE_POCKETBASE_URL;
export {
//
@@ -133,6 +134,8 @@ export {
WRONG_ANSWER_MESSAGE,
WRONG_ANS_BG_COLOR,
//
POCKETBASE_URL,
//
hide_setting,
};

View File

@@ -0,0 +1,6 @@
# GUIDELINE
- single file contains single function only
- please refer to the `tsx` files already exist in this directory for
- styling
- naming convention

View File

@@ -0,0 +1,20 @@
import { usePocketBase } from './usePocketBase';
import { useQuery } from '@tanstack/react-query';
import { CategoryType } from '../types/CategoryTypes';
const useListCategoriesByLessonId = (lessonId: string) => {
const { pb } = usePocketBase();
return useQuery({
queryKey: ['categories', 'byLessonType', lessonId],
queryFn: async () => {
return await pb.collection('LessonsCategories').getList<LessonCategory>(1, 9999, {
filter: `lesson_id = "${lessonId}"`,
sort: 'pos',
});
},
staleTime: 60 * 1000 * 5, // 5 minutes
});
};
export default useListCategoriesByLessonId;

View File

@@ -0,0 +1,19 @@
import { usePocketBase } from './usePocketBase';
import { useQuery } from '@tanstack/react-query';
const useListMatchingFrenzyCategories = () => {
const { user, pb } = usePocketBase();
return useQuery({
queryKey: ['useListMatchingFrenzyCategories'],
staleTime: 60 * 1000,
queryFn: async () => {
return await pb.collection('QuizMFCategories').getList<MatchingFrenzyCategory>(1, 9999, {
$autoCancel: false,
});
},
// enabled: !!user?.id,
});
};
export default useListMatchingFrenzyCategories;

View File

@@ -0,0 +1,26 @@
import { usePocketBase } from './usePocketBase.tsx';
import { useQuery } from '@tanstack/react-query';
// import { QuizLPQuestion } from '../types/QuizLPQuestion';
const useListQuizLPQuestionByLPCategoryId = (LPCategoryId: string) => {
const { user, pb } = usePocketBase();
return useQuery({
queryKey: ['useListQuizLPQuestionByLPCategoryId', 'feeds', 'all', user?.id || '', LPCategoryId],
staleTime: 60 * 1000,
queryFn: async ({
queryKey,
}: {
queryKey: ['useListQuizLPQuestionByLPCategoryId', 'feeds', 'all', string | null, string];
}) => {
console.log('calling useListQuizLPQuestionByLPCategoryId');
return await pb.collection('QuizLPQuestions').getList<QuizLPQuestion>(1, 9999, {
filter: `cat_id = "${LPCategoryId}"`,
sort: 'id',
$autoCancel: false,
});
},
// enabled: !!user?.id && !!LPCategoryId,
});
};
export default useListQuizLPQuestionByLPCategoryId;

View File

@@ -0,0 +1,37 @@
import { usePocketBase } from './usePocketBase.tsx';
import { useQuery } from '@tanstack/react-query';
import IListeningPracticeCategory from '../interfaces/IListeningPracticeCategory.tsx';
const useListQuizListeningPracticeContent = () => {
const { user, pb } = usePocketBase();
return useQuery({
queryKey: [
'useListQuizListeningPracticeContent',
'feeds',
'all',
user?.id || '',
//
],
staleTime: 60 * 1000,
queryFn: async ({
queryKey,
}: {
queryKey: [
'useListQuizListeningPracticeContent',
'feeds',
'all',
string | null,
//
];
}) => {
console.log('calling useListQuizListeningPracticeContent');
return await pb.collection('LessonsCategories').getList<IListeningPracticeCategory>(1, 9999, {
sort: 'pos',
$autoCancel: false,
});
},
// enabled: !!user?.id,
});
};
export default useListQuizListeningPracticeContent;

View File

@@ -1,5 +1,6 @@
import { useCallback, useState, useEffect, createContext, useContext } from 'react';
import PocketBase, { AuthRecord } from 'pocketbase';
import { POCKETBASE_URL } from '../constants';
type PocketBaseContextValue = {
pb: PocketBase;
user: AuthRecord;
@@ -7,7 +8,7 @@ type PocketBaseContextValue = {
};
const PocketBaseContext = createContext<PocketBaseContextValue | null>(null);
const POCKETBASE_URL = import.meta.env.VITE_POCKETBASE_URL;
// const POCKETBASE_URL = import.meta.env.VITE_POCKETBASE_URL;
export const PocketBaseProvider = ({ children }: { children: any }) => {
const [pb, _] = useState(new PocketBase(POCKETBASE_URL));

View File

@@ -1,6 +1,7 @@
import IListeningPracticeQuestion from './IListeningPracticeQuestion';
interface IListeningPracticeCategory {
id: string;
test_s: string;
test_i: number;
cat_info: string;

View File

@@ -25,28 +25,30 @@ import { listLessonContent } from '../../../public_data/listLessonContent';
// import { listLessonContent } from '../../../public_data/index.ts.del';
import { AudioControls } from '../../LessonWord/AudioControls';
import { getFavLessonConnectivesLink, getLessonConnectivesLink } from '../getLessonConnectivesLink';
import useGetVocabularyRoute from '../../../hooks/useGetVocabularyRoute';
const ConnectivesPage: React.FC = () => {
let [loading, setLoading] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(true);
const router = useIonRouter();
const modal = useRef<HTMLIonModalElement>(null);
const { lesson_idx, cat_idx, word_idx } = useParams<{ lesson_idx: string; cat_idx: string; word_idx: string }>();
const [open_remove_modal, setOpenRemoveModal] = useState(false);
const [lesson_info, setLessonInfo] = useState<ILesson | undefined>(undefined);
let [all_words_length, setAllWordsLength] = useState<number>(0);
let [show_word, setShowWord] = useState({ word: '', word_c: '', sample_e: '', sample_c: '', sound: '' });
let [cat_name, setCatName] = useState<string>('');
let [fav_active, setFavActive] = useState<boolean>(false);
let { playing, play: sound_play } = useGlobalAudioPlayer();
const [fav_active, setFavActive] = useState<boolean>(false);
const router = useIonRouter();
const { playing, play: sound_play } = useGlobalAudioPlayer();
const [lesson_info, setLessonInfo] = useState<ILesson | undefined>(undefined);
let { lesson_idx, cat_idx, word_idx } = useParams<{ lesson_idx: string; cat_idx: string; word_idx: string }>();
let i_lesson_idx = parseInt(lesson_idx);
let i_cat_idx = parseInt(cat_idx);
let i_word_idx = parseInt(word_idx);
const [isOpen, setIsOpen] = useState(false);
const [open_remove_modal, setOpenRemoveModal] = useState(false);
const modal = useRef<HTMLIonModalElement>(null);
// const lesson_vocab_address = `/lesson_word_page/v/${lesson_idx}/${cat_idx}/${word_idx}`;
let [favorite_address, setFavoriteAddress] = useState(getFavLessonConnectivesLink(lesson_idx, cat_idx, word_idx));
@@ -69,6 +71,8 @@ const ConnectivesPage: React.FC = () => {
const { myIonStoreAddFavoriteConnectives, myIonStoreRemoveFavoriteConnectives, myIonStoreFindInFavoriteConnectives } =
useMyIonFavorite();
let { status, data: tempResult } = useGetVocabularyRoute('000000000000002', '000000000000021');
let [in_fav, setInFav] = useState(false);
const isInFavorite = async (string_to_search: string) => {
let result = await myIonStoreFindInFavoriteConnectives(string_to_search);
@@ -113,9 +117,16 @@ const ConnectivesPage: React.FC = () => {
})();
}, [i_lesson_idx, i_cat_idx, i_word_idx]);
if (lesson_info === undefined) return <LoadingScreen />;
// if (lesson_info === undefined) return <LoadingScreen />;
// if (loading) return <LoadingScreen />;
if (loading) return <LoadingScreen />;
useEffect(() => {
if (status === 'success') {
console.log({ tempResult });
}
}, [status]);
return <>{JSON.stringify({ tempResult }, null, 2)}</>;
return (
<>
@@ -128,7 +139,7 @@ const ConnectivesPage: React.FC = () => {
fill="clear"
color={'dark'}
// href={`${LESSON_LINK}/a/${lesson_info.name}`}
onClick={() => router.push(`${LESSON_LINK}/a/${lesson_info.name}`)}
onClick={() => router.push(`${'LESSON_LINK'}/a/${'lesson_info.name'}`)}
>
<IonIcon size="large" icon={arrowBackCircleOutline} />
</IonButton>
@@ -144,10 +155,10 @@ const ConnectivesPage: React.FC = () => {
}}
>
<div>
Part {i_word_idx + 1}/{all_words_length}
Part {i_word_idx + 1}/{'all_words_length'}
</div>
</div>
{/* <LessonContainer name='Tab 1 page' /> */}
{/* */}
<div
style={{
display: 'flex',
@@ -164,7 +175,7 @@ const ConnectivesPage: React.FC = () => {
justifyContent: 'center',
fontSize: '1.3rem',
}}
dangerouslySetInnerHTML={{ __html: cat_name }}
dangerouslySetInnerHTML={{ __html: 'cat_name' }}
></div>
</div>
<div style={{ marginTop: '1rem' }}>
@@ -177,7 +188,11 @@ const ConnectivesPage: React.FC = () => {
in_fav ? handleUserRemoveFavorite() : addToFavorite(favorite_address);
}}
>
<IonIcon slot="icon-only" icon={in_fav ? heart : heartOutline}></IonIcon>
<IonIcon
slot="icon-only"
icon={in_fav ? heart : heartOutline}
//
></IonIcon>
</IonButton>
</div>
@@ -189,7 +204,6 @@ const ConnectivesPage: React.FC = () => {
justifyContent: 'space-between',
gap: '1rem',
width: '100%',
//
}}
>
@@ -217,10 +231,18 @@ const ConnectivesPage: React.FC = () => {
fontWeight: 'bold',
}}
>
<div style={{ fontSize: '1.6rem' }} dangerouslySetInnerHTML={{ __html: show_word.word }}></div>
<div style={{ fontSize: '1.6rem' }} dangerouslySetInnerHTML={{ __html: show_word.word_c }}></div>
<div
style={{ fontSize: '1.6rem' }}
dangerouslySetInnerHTML={{ __html: show_word.word }}
//
></div>
<div
style={{ fontSize: '1.6rem' }}
dangerouslySetInnerHTML={{ __html: show_word.word_c }}
//
></div>
</div>
{/* */}
<div>
<IonButton
shape="round"
@@ -234,7 +256,7 @@ const ConnectivesPage: React.FC = () => {
</IonButton>
</div>
</div>
{/* */}
<div style={{ marginTop: '2rem' }}>
<div
style={{
@@ -246,13 +268,20 @@ const ConnectivesPage: React.FC = () => {
}}
>
<div>
<IonButton shape="round" fill="clear" color="dark" disabled={playing} onClick={() => sound_play()}>
<IonButton
shape="round"
fill="clear"
color="dark"
disabled={playing}
onClick={() => sound_play()}
//
>
<IonIcon slot="icon-only" icon={playing ? playOutline : volumeHighOutline}></IonIcon>
</IonButton>
</div>
{/* <div style={{ fontSize: '1rem' }} dangerouslySetInnerHTML={{ __html: show_word.sample_e }}></div> */}
<div style={{ fontSize: '1rem' }}>
<Markdown remarkPlugins={[remarkGfm]}>{show_word.sample_e}</Markdown>
<Markdown remarkPlugins={[remarkGfm]}>{'show_word.sample_e'}</Markdown>
</div>
</div>
@@ -260,7 +289,7 @@ const ConnectivesPage: React.FC = () => {
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', margin: '1rem 1rem 0 1rem' }}
>
<div style={{ fontSize: '1.1rem' }}>
<Markdown remarkPlugins={[remarkGfm]}>{show_word.sample_c}</Markdown>
<Markdown remarkPlugins={[remarkGfm]}>{'show_word.sample_c'}</Markdown>
</div>
</div>
</div>

View File

@@ -7,41 +7,35 @@ import { useMyIonStore } from '../../../contexts/MyIonStore';
import { listLessonCategories } from '../../../public_data/listLessonCategories';
import { getLessonConnectivesLink } from '../getLessonConnectivesLink';
import { getLessonVocabularyLink } from '../getLessonWordLink';
import useListCategoriesByLessonId from '../../../hooks/useListCategoriesByLessonId';
import { getImage } from '../../../utils/getImage';
let lessonLinkProxy = [getLessonVocabularyLink, getLessonConnectivesLink];
interface ContainerProps {
test_active_lesson_idx: number;
lesson_type_id: string;
}
const LessonContainer: React.FC<ContainerProps> = ({
test_active_lesson_idx = 1,
lesson_type_id = '000000000000001',
}) => {
const LessonContainer: React.FC<ContainerProps> = ({ lesson_type_id: lesson_type_id = '000000000000001' }) => {
const router = useIonRouter();
const [loading, setLoading] = useState<boolean>(true);
const { lesson_contents, setLessonContent } = useMyIonStore();
const [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
const [selected_content, setSelectedContent] = useState<any>([]);
const result = useListCategoriesByLessonId(lesson_type_id);
useEffect(() => {
listLessonCategories().then((cats: any) => {
console.log({ cats });
setLessonContent(cats);
setActiveLessonIdx(test_active_lesson_idx);
if (result.status === 'success') {
setSelectedContent(result.data.items);
setLoading(false);
});
}, []);
}
}, [result]);
useEffect(() => {
if (loading) return;
console.log('active_category changed', active_lesson_idx);
let selected_category = lesson_contents[test_active_lesson_idx];
setSelectedContent(selected_category['content']);
}, [active_lesson_idx, loading, test_active_lesson_idx]);
// return <>helloworld</>;
if (loading) return <LoadingScreen />;
if (!selected_content) return <LoadingScreen />;
if (selected_content.length == 0) return <>loading</>;
return (
<>
@@ -61,16 +55,16 @@ const LessonContainer: React.FC<ContainerProps> = ({
style={{ width: '45vw', height: '45vw' }}
fill="clear"
onClick={() => {
// TODO: review the layout type `v` and `c`
router.push(`/lesson_word_page/v/${lesson_type_id}/${content.id}/0`, undefined, 'replace');
}}
>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div
key={cat_idx}
style={{
width: '100px',
height: '100px',
backgroundImage: `url(${content.cat_image})`,
backgroundImage: `url(${getImage(content.collectionId, content.id, content.cat_image)})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: '0.5rem',
@@ -89,3 +83,19 @@ const LessonContainer: React.FC<ContainerProps> = ({
};
export default LessonContainer;
// useEffect(() => {
// listLessonCategories().then((cats: any) => {
// console.log({ cats });
// setLessonContent(cats);
// setActiveLessonIdx(test_active_lesson_idx);
// setLoading(false);
// });
// }, []);
// useEffect(() => {
// if (loading) return;
// console.log('active_category changed', active_lesson_idx);
// let selected_category = lesson_contents[test_active_lesson_idx];
// setSelectedContent(selected_category['content']);
// }, [active_lesson_idx, loading, test_active_lesson_idx]);

View File

@@ -36,8 +36,8 @@ import { UseQueryResult } from '@tanstack/react-query';
import { ListResult } from 'pocketbase';
import LessonsTypes from '../../../types/LessonsTypes';
function WordPage(): React.JSX.Element {
const [loading, setLoading] = useState(true);
const WordPage: React.FC = () => {
const [loading, setLoading] = useState<boolean>(true);
const router = useIonRouter();
const modal = useRef<HTMLIonModalElement>(null);
@@ -45,6 +45,7 @@ function WordPage(): React.JSX.Element {
const [open_remove_modal, setOpenRemoveModal] = useState(false);
const [lesson_info, setLessonInfo] = useState<ILesson | undefined>(undefined);
const [cat_info, setCatInfo] = useState<ILessonCategory | undefined>(undefined);
const [word_info, setWordInfo] = useState<IWordCard | undefined>(undefined);
@@ -322,6 +323,6 @@ function WordPage(): React.JSX.Element {
{/* */}
</>
);
}
};
export default WordPage;

View File

@@ -28,48 +28,19 @@ import LessonContainer from './LessonContainer';
import useHelloworld from '../../hooks/useHelloworld';
import useListAllLessonTypes from '../../hooks/useListAllLessonTypes';
import LessonsTypes, { LessonsType } from '../../types/LessonsTypes';
import useListCategoriesByLessonId from '../../hooks/useListCategoriesByLessonId';
const Lesson: React.FC = () => {
const { act_category } = useParams<{ act_category: string }>();
const { t, i18n } = useTranslation(); // not passing any namespace will use the defaultNS (by default set to 'translation')
let router = useIonRouter();
let [loading, setLoading] = useState<boolean>(true);
let { lesson_contents: lesson_content, setLessonContent } = useMyIonStore();
let [active_lesson_idx, setActiveLessonIdx] = useState<number>(0);
let [selected_content, setSelectedContent] = useState<any>([]);
// useEffect(() => {
// listLessonCategories().then((cats: any) => {
// console.log({ cats });
// setLessonContent(cats);
// setActiveLessonIdx(0);
// setLoading(false);
// });
// }, []);
useEffect(() => {
if (loading) return;
console.log('active_category changed', active_lesson_idx);
let selected_category = lesson_content[active_lesson_idx];
setSelectedContent(selected_category['content']);
}, [active_lesson_idx, loading]);
let router = useIonRouter();
useEffect(() => {
if (lesson_content.length > 0) {
if (act_category == undefined) {
router.push(`${LESSON_LINK}/a/${lesson_content[0].name}`, undefined, 'replace');
} else {
setActiveLessonIdx(
lesson_content.findIndex((cat: any) => cat.name.toLowerCase() === act_category?.toLowerCase()),
);
}
} else {
}
}, [act_category, lesson_content]);
const lessonTypesQuery = useListAllLessonTypes();
const [lessonTypes, setLessonTypes] = useState<LessonsType[]>([]);
useEffect(() => {
if (lessonTypesQuery.status === 'success') {
const { data: tempLessonTypes } = lessonTypesQuery;
@@ -82,10 +53,8 @@ const Lesson: React.FC = () => {
}
}, [lessonTypesQuery]);
// return <pre>{JSON.stringify({ lessonTypes }, null, 2)}</pre>;
if (loading) return <LoadingScreen />;
if (lessonTypes.length === 0) return <LoadingScreen />;
// return <pre>{JSON.stringify({ t: lessonTypes }, null, 2)}</pre>;
return (
<IonPage>
@@ -140,10 +109,12 @@ const Lesson: React.FC = () => {
</IonList>
</div>
{/* */}
<LessonContainer
test_active_lesson_idx={active_lesson_idx}
lesson_type_id={lessonTypes[active_lesson_idx].id}
/>
{/* FIX: */}
{lessonTypes[active_lesson_idx]?.id ? (
<LessonContainer lesson_type_id={lessonTypes[active_lesson_idx].id} />
) : (
<>loading (id undefined)</>
)}
{/* */}
<CongratGenius />
<CongratHardworker />
@@ -156,3 +127,58 @@ const Lesson: React.FC = () => {
};
export default Lesson;
// let { lesson_contents: lesson_content, setLessonContent } = useMyIonStore();
// // TODO: remove me
// // useEffect(() => {
// // listLessonCategories().then((cats: any) => {
// // console.log({ cats });
// // setLessonContent(cats);
// // setActiveLessonIdx(0);
// // setLoading(false);
// // });
// // }, []);
// useEffect(() => {
// if (loading) return;
// console.log('active_category changed', lesson_content[active_lesson_idx]);
// // FIX:
// // let selected_category = lesson_content[active_lesson_idx];
// // setSelectedContent(selected_category['content']);
// // let categories = await getCategoryByLessonType(lessonType)
// //
// // useListCategoriesByLessonType(lessonType)
// }, [active_lesson_idx, loading]);
// useEffect(() => {
// if (lesson_content.length > 0) {
// if (act_category == undefined) {
// router.push(`${LESSON_LINK}/a/${lesson_content[0].name}`, undefined, 'replace');
// } else {
// setActiveLessonIdx(
// lesson_content.findIndex((cat: any) => cat.name.toLowerCase() === act_category?.toLowerCase()),
// );
// }
// } else {
// }
// }, [act_category, lesson_content]);
// const lessonTypesQuery = useListAllLessonTypes();
// const [lessonTypes, setLessonTypes] = useState<LessonsType[]>([]);
// useEffect(() => {
// if (lessonTypesQuery.status === 'success') {
// const { data: tempLessonTypes } = lessonTypesQuery;
// console.log({ tempLessonTypes });
// if (tempLessonTypes) {
// setLessonTypes(tempLessonTypes);
// }
// setLoading(false);
// }
// }, [lessonTypesQuery]);
// if (loading) return <LoadingScreen />;
// // return <pre>{JSON.stringify({ t: lessonTypes }, null, 2)}</pre>;
// if (lessonTypes.length === 0) return <LoadingScreen />;

View File

@@ -16,6 +16,9 @@ import CorrectionCard from './CorrectionCard';
import './style.css';
const CorrectionRoute: React.FC = () => {
// TODO: not yet implemented
return <>not yet implemented</>;
const router = useIonRouter();
const { p_route } = useParams<{ p_route: string }>();
const i_p_route = parseInt(p_route);

View File

@@ -10,8 +10,10 @@ import IListeningPracticeQuestion from '../../../interfaces/IListeningPracticeQu
import { listQuizListeningPracticeContent } from '../../../public_data/listQuizListeningPracticeContent';
import { shuffleArray } from '../../../utils/shuffleArray';
import ListeningPracticeQuestionCard from './ListeningPracticeQuestionCard';
import useListQuizLPQuestionByLPCategoryId from '../../../hooks/useListQuizLPQuestionByLPCategoryId';
const QuestionRoute: React.FC = () => {
const router = useIonRouter();
const { p_route } = useParams<{ p_route: string }>();
const i_p_route = parseInt(p_route);
const [question_list, setQuestionList] = useState<IListeningPracticeQuestion[] | []>([]);
@@ -26,7 +28,7 @@ const QuestionRoute: React.FC = () => {
const { listening_practice_correction_list, setListeningPracticeCorrectionList } = useMyIonQuizContext();
const [question_finished, setQuestionFinished] = useState(false);
const router = useIonRouter();
let [answer_list, setAnswerList] = useState<string[]>([]);
const nextQuestion = () => {
if (current_question_idx >= question_list.length - 1) {
setQuestionFinished(true);
@@ -64,21 +66,27 @@ const QuestionRoute: React.FC = () => {
myIonMetricIncFullMarkCount();
};
let [answer_list, setAnswerList] = useState<string[]>([]);
let [questionList, setLPQuestionList] = useState<QuizLPQuestion[]>([]);
useEffect(() => {
(async () => {
const res_json = await listQuizListeningPracticeContent();
let init_answer = res_json[i_p_route].init_answer;
let temp = res_json[i_p_route].content;
if (questionList.length > 0) {
let temp = questionList;
let init_answer = temp[0].init_answer;
let shuffled_temp = shuffleArray(temp);
//
setQuestionList(shuffled_temp);
setAnswerList([...new Set([...init_answer, ...temp.map((t: IListeningPracticeQuestion) => t.word)])]);
setCurrentQuestionMeta(res_json[i_p_route].content[current_question_idx]);
})();
}, []);
setAnswerList([...new Set([...init_answer, ...temp.map((t) => t.word)])]);
setCurrentQuestionMeta(temp[current_question_idx] as unknown as IListeningPracticeQuestion);
// console.log({ t: result.data.items[0] });
}
}, [questionList]);
let result = useListQuizLPQuestionByLPCategoryId(p_route);
useEffect(() => {
if (result.status === 'success') {
//
setLPQuestionList(result.data.items);
}
}, [result]);
if (!current_question_meta) return <LoadingScreen />;

View File

@@ -8,6 +8,7 @@ import { useAppStateContext } from '../../contexts/AppState';
import IListeningPracticeCategory from '../../interfaces/IListeningPracticeCategory';
import { listQuizListeningPracticeContent } from '../../public_data/listQuizListeningPracticeContent';
import { useTranslation } from 'react-i18next';
import useListQuizListeningPracticeContent from '../../hooks/useListQuizListeningPracticeContent';
// import { listQuizListeningPracticeContent } from '../../public_data/listQuizListeningPracticeContent';
@@ -17,19 +18,27 @@ const ListeningPractice: React.FC = () => {
let [categories, setCategories] = useState<IListeningPracticeCategory[] | []>([]);
let { show_confirm_user_exit, setShowConfirmUserExit, setTabActive } = useAppStateContext();
// useEffect(() => {
// listQuizListeningPracticeContent().then((res_json: any) => {
// setCategories(res_json);
// DEBUG ? console.log({ res_json }) : '';
// setLoading(false);
// });
// setTabActive(QUIZ_MAIN_MENU_LINK);
// }, []);
let result = useListQuizListeningPracticeContent();
useEffect(() => {
listQuizListeningPracticeContent().then((res_json: any) => {
setCategories(res_json);
DEBUG ? console.log({ res_json }) : '';
if (result.status == 'success') {
setCategories(result.data.items);
setLoading(false);
});
setTabActive(QUIZ_MAIN_MENU_LINK);
}, []);
}
}, [result]);
let { setListeningPracticeInProgress } = useAppStateContext();
let router = useIonRouter();
function startListeningPractice(idx: number) {
let url = `${LISTENING_PRACTICE_LINK}/r/${idx}`;
let url = `${LISTENING_PRACTICE_LINK}/r/${categories[idx].id}`;
setListeningPracticeInProgress(true);
router.push(url, 'none', 'replace');
}
@@ -38,6 +47,8 @@ const ListeningPractice: React.FC = () => {
if (loading) return <LoadingScreen />;
// return <>{JSON.stringify({ categories }, null, 2)}</>;
return (
<IonPage>
<IonHeader className="ion-no-border">

View File

@@ -6,23 +6,38 @@ import { DEBUG, MATCHING_FRENZY_LINK, QUIZ_MAIN_MENU_LINK } from '../../constant
import { useAppStateContext } from '../../contexts/AppState';
import IMatchingFrenzyCategory from '../../interfaces/IMatchingFrenzyCategory';
import { listMatchingFrenzyContent } from '../../public_data/listMatchingFrenzyContent';
import useListMatchingFrenzyCategories from '../../hooks/useListMatchingFrenzyContent';
function MatchingFrenzySelectCategory() {
const PAGE_TITLE = 'Matching Frenzy';
const router = useIonRouter();
const [loading, setLoading] = useState<boolean>(true);
const [categories, setCategories] = useState<IMatchingFrenzyCategory[] | []>([]);
const [loadedCategories, setLoadedCategories] = useState<MatchingFrenzyCategory[]>([]);
const { setTabActive } = useAppStateContext();
useEffect(() => {
listMatchingFrenzyContent().then((res_json: any) => {
setCategories(res_json);
DEBUG ? console.log({ res_json }) : '';
setLoading(false);
});
// useEffect(() => {
// listMatchingFrenzyContent().then((res_json: any) => {
// setCategories(res_json);
// DEBUG ? console.log({ res_json }) : '';
// setLoading(false);
// });
setTabActive(QUIZ_MAIN_MENU_LINK);
}, []);
// setTabActive(QUIZ_MAIN_MENU_LINK);
// }, []);
let [temp, setTemp] = useState();
let result = useListMatchingFrenzyCategories();
useEffect(() => {
if (result.status === 'success') {
// setCategories(result.data.items);
// setTemp(result.data.items)
setLoadedCategories(result.data.items);
setLoading(false);
}
}, [result]);
// return <>helloworld</>;
if (loading) return <LoadingScreen />;
@@ -75,23 +90,26 @@ function MatchingFrenzySelectCategory() {
</div>
<div>Choose the Chapter you want to revise</div>
<div style={{ width: '80vw' }}>
{categories
{loadedCategories
.map((item) => item.cat_name)
.map((item_name, idx) => (
<div style={{ margin: '0.9rem 0 0.9rem' }} key={idx}>
<IonButton
color="dark"
fill="outline"
expand="block"
// href={`${MATCHING_FRENZY_LINK}/r/${idx}`}
onClick={() => {
router.push(`${MATCHING_FRENZY_LINK}/r/${idx}`);
}}
>
{item_name}
</IonButton>
</div>
))}
.map((item_name, idx) => {
let mf_idx = loadedCategories[idx].id;
return (
<div style={{ margin: '0.9rem 0 0.9rem' }} key={idx}>
<IonButton
color="dark"
fill="outline"
expand="block"
// href={`${MATCHING_FRENZY_LINK}/r/${idx}`}
onClick={() => {
router.push(`${MATCHING_FRENZY_LINK}/r/${mf_idx}`);
}}
>
{item_name}
</IonButton>
</div>
);
})}
</div>
</div>
</IonContent>

View File

@@ -0,0 +1,8 @@
export interface CategoryType {
id: string;
name: string;
lessonType: string;
created: string;
updated: string;
// Add other fields as needed based on actual database schema
}

View File

@@ -0,0 +1,16 @@
type MatchingFrenzyCategory = {
//
id: string;
collectionId: string;
//
cat_name: string;
cat_image_url?: string;
cat_image?: string;
pos: number;
visible: string;
lesson_id: string;
description: string;
remarks: string;
slug: string;
init_answer: any;
};

View File

@@ -0,0 +1,20 @@
// QuizLPQuestion
type QuizLPQuestion = {
//
id: string;
collectionId: string;
//
word: string;
//
cat_name: string;
cat_image_url?: string;
cat_image?: string;
pos: number;
visible: string;
lesson_id: string;
description: string;
remarks: string;
slug: string;
init_answer: any;
//
};

View File

@@ -0,0 +1,22 @@
Table QuizLPQuestion {
id string [primary key]
collectionId string
createdAt datetime
questionText text
audioUrl string
options json
correctAnswer integer
LPCategoryId string [ref: > QuizLPCategory.id]
difficulty enum('easy', 'medium', 'hard')
explanation text [null]
status enum('draft', 'published', 'archived')
indexes {
LPCategoryId
status
createdAt
}
Note: 'Stores listening practice quiz questions'
}

View File

@@ -0,0 +1,3 @@
# GUIDELINES
- please follow to the `schema.dbml` when you draft types related to db

View File

@@ -0,0 +1,6 @@
import { POCKETBASE_URL } from '../constants';
export function getImage(collectionId: string, recordId: string, fileName: string) {
console.log({ collectionId, recordId, fileName });
return `${POCKETBASE_URL}/api/files/${collectionId}/${recordId}/${fileName}`;
}