update,
This commit is contained in:
@@ -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>
|
||||
|
3
002_source/ionic_mobile/src/_GUIDELINE.md
Normal file
3
002_source/ionic_mobile/src/_GUIDELINE.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# GUIDELINE
|
||||
|
||||
`<base_dir>/002_source/ionic_mobile/src/RouteConfig.tsx` contains the route of the application.
|
@@ -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,
|
||||
};
|
||||
|
||||
|
6
002_source/ionic_mobile/src/hooks/_GUIDELINES.md
Normal file
6
002_source/ionic_mobile/src/hooks/_GUIDELINES.md
Normal 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
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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));
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import IListeningPracticeQuestion from './IListeningPracticeQuestion';
|
||||
|
||||
interface IListeningPracticeCategory {
|
||||
id: string;
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
cat_info: string;
|
||||
|
@@ -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>
|
||||
|
@@ -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]);
|
||||
|
@@ -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;
|
||||
|
@@ -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 />;
|
||||
|
@@ -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);
|
@@ -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 />;
|
||||
|
@@ -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">
|
||||
|
@@ -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>
|
||||
|
8
002_source/ionic_mobile/src/types/CategoryTypes.ts
Normal file
8
002_source/ionic_mobile/src/types/CategoryTypes.ts
Normal 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
|
||||
}
|
16
002_source/ionic_mobile/src/types/MatchingFrenzyCategory.d.ts
vendored
Normal file
16
002_source/ionic_mobile/src/types/MatchingFrenzyCategory.d.ts
vendored
Normal 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;
|
||||
};
|
20
002_source/ionic_mobile/src/types/QuizLPQuestion.d.ts
vendored
Normal file
20
002_source/ionic_mobile/src/types/QuizLPQuestion.d.ts
vendored
Normal 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;
|
||||
//
|
||||
};
|
22
002_source/ionic_mobile/src/types/QuizLPQuestion.dbml
Normal file
22
002_source/ionic_mobile/src/types/QuizLPQuestion.dbml
Normal 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'
|
||||
}
|
3
002_source/ionic_mobile/src/types/_GUIDELINES.md
Normal file
3
002_source/ionic_mobile/src/types/_GUIDELINES.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# GUIDELINES
|
||||
|
||||
- please follow to the `schema.dbml` when you draft types related to db
|
6
002_source/ionic_mobile/src/utils/getImage.ts
Normal file
6
002_source/ionic_mobile/src/utils/getImage.ts
Normal 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}`;
|
||||
}
|
Reference in New Issue
Block a user