diff --git a/app/(tabs)/subjects/page.tsx b/app/(tabs)/subjects/page.tsx index 7104faf..fcf0f1d 100644 --- a/app/(tabs)/subjects/page.tsx +++ b/app/(tabs)/subjects/page.tsx @@ -6,159 +6,70 @@ import { useEffect, useState } from "react"; import Header from "@/components/Header"; import DestructibleAlert from "@/components/DestructibleAlert"; import BackgroundWrapper from "@/components/BackgroundWrapper"; -import { API_URL } from "@/lib/auth"; +import { API_URL, getToken } from "@/lib/auth"; import { Loader, RefreshCw } from "lucide-react"; import { v4 as uuidv4 } from "uuid"; import { useAuth } from "@/context/AuthContext"; +import { Question } from "@/types/exam"; -interface Mock { - id: string; - title: string; - rating: number; -} -export default function PaperScreen() { - const router = useRouter(); - const searchParams = useSearchParams(); - const name = searchParams.get("name") || ""; - const {user} = useAuth() - - const [questions, setQuestions] = useState(null); - const [errorMsg, setErrorMsg] = useState(null); - const [refreshing, setRefreshing] = useState(false); - const [componentKey, setComponentKey] = useState(0); - -interface Subject { +type Subject = { subject_id: string; name: string; unit: string; -} +}; -const [subjects, setSubjects] = useState([ - { - subject_id: uuidv4(), - name: "Biology", - unit: "Science" - }, - { - subject_id: uuidv4(), - name: "Chemistry", - unit: "Science" - }, - { - subject_id: uuidv4(), - name: "Physics", - unit: "Science" - }, - { - subject_id: uuidv4(), - name: "Accounting", - unit: "Business" - }, - { - subject_id: uuidv4(), - name: "Finance", - unit: "Business" - }, - { - subject_id: uuidv4(), - name: "Marketing", - unit: "Business" - }, - { - subject_id: uuidv4(), - name: "History", - unit: "Humanities" - }, - { - subject_id: uuidv4(), - name: "Geography", - unit: "Humanities" - }, - { - subject_id: uuidv4(), - name: "Sociology", - unit: "Humanities" - }, -]); +export default function PaperScreen() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { user } = useAuth(); + const [subjects, setSubjects] = useState([]); + const [errorMsg, setErrorMsg] = useState(null); + const [refreshing, setRefreshing] = useState(false); + async function fetchSubjects() { + setRefreshing(true); + try { + const token = await getToken(); + const response = await fetch(`${API_URL}/subjects/`, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (!response.ok) { + throw new Error("Failed to fetch subjects"); + } - // async function fetchMocks() { - // try { - // const questionResponse = await fetch(`${API_URL}/mocks`, { - // method: "GET", - // }); - // const fetchedQuestionData: Mock[] = await questionResponse.json(); - // setQuestions(fetchedQuestionData); - // } catch (error) { - // setErrorMsg(error instanceof Error ? error.message : "An error occurred"); - // } - // } + const fetchedSubjects: Subject[] = await response.json(); + setSubjects(fetchedSubjects); + } catch (error) { + setErrorMsg( + "Error fetching subjects: " + + (error instanceof Error ? error.message : "Unknown error") + ); + } finally { + setRefreshing(false); + } + } - // useEffect(() => { - // if (name) { - // fetchMocks(); - // } - // }, [name]); + useEffect(() => { + const fetchData = async () => { + if (await getToken()) { + fetchSubjects(); + } + }; + fetchData(); + }, []); const onRefresh = async () => { - setRefreshing(true); - - setSubjects([{ - subject_id: uuidv4(), - name: "Biology", - unit: "Science" - }, - { - subject_id: uuidv4(), - name: "Chemistry", - unit: "Science" - }, - { - subject_id: uuidv4(), - name: "Physics", - unit: "Science" - }, - { - subject_id: uuidv4(), - name: "Accounting", - unit: "Business Studies" - }, - { - subject_id: uuidv4(), - name: "Finance", - unit: "Business Studies" - }, - { - subject_id: uuidv4(), - name: "Marketing", - unit: "Business Studies" - }, - { - subject_id: uuidv4(), - name: "History", - unit: "Humanities" - }, - { - subject_id: uuidv4(), - name: "Geography", - unit: "Humanities" - }, - { - subject_id: uuidv4(), - name: "Sociology", - unit: "Humanities" - }]) - setComponentKey((prevKey) => prevKey + 1); - setTimeout(() => { - setRefreshing(false); - }, 1000); + fetchSubjects(); }; if (errorMsg) { return ( -
+
@@ -182,32 +93,32 @@ const [subjects, setSubjects] = useState([
-

{user?.preparation_unit}

+

+ {user?.preparation_unit} +

- {subjects ? ( + {subjects.length > 0 ? ( subjects - .filter(subject => subject.unit === user?.preparation_unit) - .map((subject) => ( -
- -
- )) + .filter((subject) => subject.unit === user?.preparation_unit) + .map((subject) => ( +
+ +
+ )) ) : (
-
-

Loading...

+
+

Loading...

)}
diff --git a/app/exam/pretest/page.tsx b/app/exam/pretest/page.tsx index eaf6434..65f008f 100644 --- a/app/exam/pretest/page.tsx +++ b/app/exam/pretest/page.tsx @@ -7,27 +7,17 @@ import DestructibleAlert from "@/components/DestructibleAlert"; import BackgroundWrapper from "@/components/BackgroundWrapper"; import { API_URL } from "@/lib/auth"; import { useExam } from "@/context/ExamContext"; -import { Exam } from "@/types/exam"; - -interface Metadata { - metadata: { - quantity: number; - type: string; - duration: number; - marking: string; - }; -} +import { Test } from "@/types/exam"; +import { Metadata } from "@/types/exam"; function PretestPageContent() { const router = useRouter(); const searchParams = useSearchParams(); - const [examData, setExamData] = useState(); + const [examData, setExamData] = useState(); const { startExam, setCurrentExam } = useExam(); // Get params from URL search params const id = searchParams.get("id") || ""; - const title = searchParams.get("title") || ""; - const rating = searchParams.get("rating") || ""; const [metadata, setMetadata] = useState(null); const [loading, setLoading] = useState(true); @@ -67,7 +57,7 @@ function PretestPageContent() {
- @@ -77,6 +67,24 @@ function PretestPageContent() { ); } + if (loading) { + return ( + +
+
+ +
+
+

Loading...

+
+
+
+
+ ); + } + function handleStartExam() { if (!examData) return; diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx index c4b22d9..f411d03 100644 --- a/context/AuthContext.tsx +++ b/context/AuthContext.tsx @@ -72,7 +72,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ if (!res.ok) { throw new Error("Failed to fetch user info"); } - console.log(API_URL) const data: UserData = await res.json(); setUser(data); } catch (error) { diff --git a/public/data/questions.json b/public/data/questions.json index e69de29..18d391c 100644 --- a/public/data/questions.json +++ b/public/data/questions.json @@ -0,0 +1,175 @@ +{ + "questions": [ + { + "correct_answer": "Assets = Liabilities + Equity", + "difficulty": "Easy", + "explanation": "This is the basic accounting equation.", + "is_randomized": true, + "options": [ + "Assets = Liabilities + Equity", + "Assets = Revenue - Expenses", + "Assets = Liabilities - Equity", + "Assets = Equity - Liabilities" + ], + "question": "What is the basic accounting equation?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "a1b2c3d4-e5f6-7890-abcd-1234567890ab", + "type": "Single" + }, + { + "correct_answer": "Balance Sheet", + "difficulty": "Easy", + "explanation": "A balance sheet shows a company's assets, liabilities, and equity at a specific point in time.", + "is_randomized": true, + "options": [ + "Balance Sheet", + "Income Statement", + "Cash Flow Statement", + "Statement of Retained Earnings" + ], + "question": "Which financial statement shows a company's assets, liabilities, and equity?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "b2c3d4e5-f678-9012-abcd-2345678901bc", + "type": "Single" + }, + { + "correct_answer": "Revenue - Expenses", + "difficulty": "Easy", + "explanation": "Net income is calculated by subtracting expenses from revenue.", + "is_randomized": true, + "options": [ + "Revenue - Expenses", + "Assets - Liabilities", + "Equity + Liabilities", + "Revenue + Expenses" + ], + "question": "How is net income calculated?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "c3d4e5f6-7890-1234-abcd-3456789012cd", + "type": "Single" + }, + { + "correct_answer": "Accounts Receivable", + "difficulty": "Easy", + "explanation": "Accounts receivable represents money owed to a company by its customers.", + "is_randomized": true, + "options": [ + "Accounts Receivable", + "Accounts Payable", + "Inventory", + "Prepaid Expenses" + ], + "question": "Which account represents money owed to a company by its customers?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "d4e5f678-9012-3456-abcd-4567890123de", + "type": "Single" + }, + { + "correct_answer": "Depreciation", + "difficulty": "Medium", + "explanation": "Depreciation is the allocation of the cost of a tangible asset over its useful life.", + "is_randomized": true, + "options": [ + "Depreciation", + "Amortization", + "Depletion", + "Appreciation" + ], + "question": "What is the allocation of the cost of a tangible asset over its useful life called?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "e5f67890-1234-5678-abcd-5678901234ef", + "type": "Single" + }, + { + "correct_answer": "Accrual Basis", + "difficulty": "Medium", + "explanation": "Accrual basis accounting recognizes revenues and expenses when they are incurred, not when cash is exchanged.", + "is_randomized": true, + "options": [ + "Accrual Basis", + "Cash Basis", + "Modified Cash Basis", + "Tax Basis" + ], + "question": "Which accounting method recognizes revenues and expenses when they are incurred, regardless of when cash is exchanged?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "f6789012-3456-7890-abcd-6789012345fa", + "type": "Single" + }, + { + "correct_answer": "Liabilities", + "difficulty": "Easy", + "explanation": "Liabilities are obligations that a company owes to outside parties.", + "is_randomized": true, + "options": [ + "Liabilities", + "Assets", + "Equity", + "Revenue" + ], + "question": "What are obligations that a company owes to outside parties called?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "a7890123-4567-8901-abcd-7890123456ab", + "type": "Single" + }, + { + "correct_answer": "Double-entry", + "difficulty": "Medium", + "explanation": "Double-entry accounting means every transaction affects at least two accounts.", + "is_randomized": true, + "options": [ + "Double-entry", + "Single-entry", + "Triple-entry", + "Quadruple-entry" + ], + "question": "What is the accounting system called where every transaction affects at least two accounts?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "b8901234-5678-9012-abcd-8901234567bc", + "type": "Single" + }, + { + "correct_answer": "Owner's Equity", + "difficulty": "Easy", + "explanation": "Owner's equity represents the owner's claims to the assets of the business.", + "is_randomized": true, + "options": [ + "Owner's Equity", + "Liabilities", + "Revenue", + "Expenses" + ], + "question": "What is the owner's claim to the assets of a business called?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "c9012345-6789-0123-abcd-9012345678cd", + "type": "Single" + }, + { + "correct_answer": "Matching Principle", + "difficulty": "Medium", + "explanation": "The matching principle requires that expenses be matched with related revenues.", + "is_randomized": true, + "options": [ + "Matching Principle", + "Revenue Recognition Principle", + "Cost Principle", + "Conservatism Principle" + ], + "question": "Which accounting principle requires that expenses be matched with related revenues?", + "source_type": "Mock", + "subject_id": "f1c1d54a-0b29-44e2-8f95-ecf9f9e6d8b7", + "topic_id": "d0123456-7890-1234-abcd-0123456789de", + "type": "Single" + } +] + +} \ No newline at end of file diff --git a/types/exam.d.ts b/types/exam.d.ts index ad9f1f7..bf47598 100644 --- a/types/exam.d.ts +++ b/types/exam.d.ts @@ -1,55 +1,62 @@ -export interface Question { - id: string; - question: string; - options?: Record; - type: "multiple-choice" | "text" | "boolean" | undefined; - correctAnswer: string | undefined; - solution?: string | undefined; +export type Metadata = { + test_id: string; + total_possible_score: number; + deduction: number; + num_questions: number; + name: string; + start_time: Date; + time_limit_minutes: number; } -export interface Exam { - id: string; - title: string; - description: string; +export type Question = { + question_id: string; + question: string; + options: string[]; + type: string; +} + +export interface Test { + metadata: Metadata; questions: Question[]; - timeLimit?: number; - passingScore?: number; } -export interface ExamAnswer { - questionId: string; - answer: string; - timestamp: Date; -} +type Answers = number | null; -export interface ExamAttempt { - examId: string; - exam: Exam; - answers: ExamAnswer[]; - startTime: Date; - endTime?: Date; - score: number; - passed?: boolean; - apiResponse?: any; - totalQuestions: number; +export interface TestAttempt { + user_id: string; + test_id: string; + attempt_id: string; + start_time: Date; + end_time: Date; + user_questions: { + question_id: string; + question: string; + options: string[]; + type: string; + }; + user_answers: Record; + correct_answers: Record; + correct_answers_count: number; + wrong_answers_count: number; + skipped_questions_count: number; } export interface ExamContextType { - currentExam: Exam | null; - currentAttempt: ExamAttempt | null; + currentExam: Test | null; + currentAttempt: TestAttempt | null; isHydrated: boolean; isInitialized: boolean; // Actions - setCurrentExam: (exam: Exam) => void; - startExam: (exam?: Exam) => void; - setAnswer: (questionId: string, answer: any) => void; - submitExam: () => ExamAttempt | null; + setCurrentExam: (exam: Test) => void; + startExam: (exam?: Test) => void; + setAnswer: (questionId: string, answer: Answers) => void; + submitExam: () => TestAttempt | null; clearExam: () => void; setApiResponse: (response: any) => void; // Getters - getAnswer: (questionId: string) => any; + getAnswer: (questionId: string) => Answers; getProgress: () => number; isExamStarted: () => boolean; isExamCompleted: () => boolean;