@octo-cyber/quiz-engine 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/attempt.controller.d.ts +3 -0
- package/dist/controllers/attempt.controller.d.ts.map +1 -0
- package/dist/controllers/attempt.controller.js +83 -0
- package/dist/controllers/attempt.controller.js.map +1 -0
- package/dist/controllers/category.controller.d.ts +3 -0
- package/dist/controllers/category.controller.d.ts.map +1 -0
- package/dist/controllers/category.controller.js +38 -0
- package/dist/controllers/category.controller.js.map +1 -0
- package/dist/controllers/exam.controller.d.ts +3 -0
- package/dist/controllers/exam.controller.d.ts.map +1 -0
- package/dist/controllers/exam.controller.js +48 -0
- package/dist/controllers/exam.controller.js.map +1 -0
- package/dist/controllers/index.d.ts +7 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/index.js +16 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/paper.controller.d.ts +3 -0
- package/dist/controllers/paper.controller.d.ts.map +1 -0
- package/dist/controllers/paper.controller.js +70 -0
- package/dist/controllers/paper.controller.js.map +1 -0
- package/dist/controllers/question.controller.d.ts +3 -0
- package/dist/controllers/question.controller.d.ts.map +1 -0
- package/dist/controllers/question.controller.js +59 -0
- package/dist/controllers/question.controller.js.map +1 -0
- package/dist/controllers/statistics.controller.d.ts +3 -0
- package/dist/controllers/statistics.controller.d.ts.map +1 -0
- package/dist/controllers/statistics.controller.js +37 -0
- package/dist/controllers/statistics.controller.js.map +1 -0
- package/dist/entities/index.d.ts +24 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +43 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/quiz-answer.entity.d.ts +19 -0
- package/dist/entities/quiz-answer.entity.d.ts.map +1 -0
- package/dist/entities/quiz-answer.entity.js +81 -0
- package/dist/entities/quiz-answer.entity.js.map +1 -0
- package/dist/entities/quiz-attempt.entity.d.ts +17 -0
- package/dist/entities/quiz-attempt.entity.d.ts.map +1 -0
- package/dist/entities/quiz-attempt.entity.js +80 -0
- package/dist/entities/quiz-attempt.entity.js.map +1 -0
- package/dist/entities/quiz-category.entity.d.ts +10 -0
- package/dist/entities/quiz-category.entity.d.ts.map +1 -0
- package/dist/entities/quiz-category.entity.js +55 -0
- package/dist/entities/quiz-category.entity.js.map +1 -0
- package/dist/entities/quiz-exam.entity.d.ts +25 -0
- package/dist/entities/quiz-exam.entity.d.ts.map +1 -0
- package/dist/entities/quiz-exam.entity.js +99 -0
- package/dist/entities/quiz-exam.entity.js.map +1 -0
- package/dist/entities/quiz-paper-question.entity.d.ts +12 -0
- package/dist/entities/quiz-paper-question.entity.d.ts.map +1 -0
- package/dist/entities/quiz-paper-question.entity.js +58 -0
- package/dist/entities/quiz-paper-question.entity.js.map +1 -0
- package/dist/entities/quiz-paper.entity.d.ts +18 -0
- package/dist/entities/quiz-paper.entity.d.ts.map +1 -0
- package/dist/entities/quiz-paper.entity.js +75 -0
- package/dist/entities/quiz-paper.entity.js.map +1 -0
- package/dist/entities/quiz-question.entity.d.ts +28 -0
- package/dist/entities/quiz-question.entity.d.ts.map +1 -0
- package/dist/entities/quiz-question.entity.js +107 -0
- package/dist/entities/quiz-question.entity.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/quiz-engine.module.d.ts +8 -0
- package/dist/quiz-engine.module.d.ts.map +1 -0
- package/dist/quiz-engine.module.js +45 -0
- package/dist/quiz-engine.module.js.map +1 -0
- package/dist/schemas/attempt.schema.d.ts +47 -0
- package/dist/schemas/attempt.schema.d.ts.map +1 -0
- package/dist/schemas/attempt.schema.js +19 -0
- package/dist/schemas/attempt.schema.js.map +1 -0
- package/dist/schemas/category.schema.d.ts +36 -0
- package/dist/schemas/category.schema.d.ts.map +1 -0
- package/dist/schemas/category.schema.js +12 -0
- package/dist/schemas/category.schema.js.map +1 -0
- package/dist/schemas/exam.schema.d.ts +70 -0
- package/dist/schemas/exam.schema.d.ts.map +1 -0
- package/dist/schemas/exam.schema.js +20 -0
- package/dist/schemas/exam.schema.js.map +1 -0
- package/dist/schemas/paper.schema.d.ts +71 -0
- package/dist/schemas/paper.schema.d.ts.map +1 -0
- package/dist/schemas/paper.schema.js +26 -0
- package/dist/schemas/paper.schema.js.map +1 -0
- package/dist/schemas/question.schema.d.ts +147 -0
- package/dist/schemas/question.schema.d.ts.map +1 -0
- package/dist/schemas/question.schema.js +32 -0
- package/dist/schemas/question.schema.js.map +1 -0
- package/dist/services/attempt.service.d.ts +33 -0
- package/dist/services/attempt.service.d.ts.map +1 -0
- package/dist/services/attempt.service.js +197 -0
- package/dist/services/attempt.service.js.map +1 -0
- package/dist/services/category.service.d.ts +14 -0
- package/dist/services/category.service.d.ts.map +1 -0
- package/dist/services/category.service.js +74 -0
- package/dist/services/category.service.js.map +1 -0
- package/dist/services/exam.service.d.ts +17 -0
- package/dist/services/exam.service.d.ts.map +1 -0
- package/dist/services/exam.service.js +92 -0
- package/dist/services/exam.service.js.map +1 -0
- package/dist/services/grade.service.d.ts +16 -0
- package/dist/services/grade.service.d.ts.map +1 -0
- package/dist/services/grade.service.js +75 -0
- package/dist/services/grade.service.js.map +1 -0
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/paper.service.d.ts +32 -0
- package/dist/services/paper.service.d.ts.map +1 -0
- package/dist/services/paper.service.js +157 -0
- package/dist/services/paper.service.js.map +1 -0
- package/dist/services/question.service.d.ts +30 -0
- package/dist/services/question.service.d.ts.map +1 -0
- package/dist/services/question.service.js +155 -0
- package/dist/services/question.service.js.map +1 -0
- package/dist/services/statistics.service.d.ts +43 -0
- package/dist/services/statistics.service.d.ts.map +1 -0
- package/dist/services/statistics.service.js +134 -0
- package/dist/services/statistics.service.js.map +1 -0
- package/package.json +85 -0
- package/web/index.ts +51 -0
- package/web/manifest.ts +36 -0
- package/web/messages/en-US.json +143 -0
- package/web/messages/zh-CN.json +143 -0
- package/web/pages/ExamRoomPage.tsx +289 -0
- package/web/pages/ExamsPage.tsx +248 -0
- package/web/pages/PapersPage.tsx +202 -0
- package/web/pages/QuestionBankPage.tsx +263 -0
- package/web/pages/StatisticsPage.tsx +178 -0
- package/web/services/attempt-service.ts +53 -0
- package/web/services/category-service.ts +26 -0
- package/web/services/exam-service.ts +31 -0
- package/web/services/paper-service.ts +50 -0
- package/web/services/question-service.ts +36 -0
- package/web/services/statistics-service.ts +28 -0
- package/web/stores/quiz-store.ts +31 -0
- package/web/types/quiz.ts +166 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
import type { QuizAttempt, QuizAnswer, AttemptDetail, PaginatedResponse } from '../types/quiz'
|
|
4
|
+
|
|
5
|
+
export const attemptApi = {
|
|
6
|
+
async start(examId: string): Promise<QuizAttempt> {
|
|
7
|
+
const res = await api.post<ApiResponse<QuizAttempt>>('/api/v1/quiz/attempts/start', { examId })
|
|
8
|
+
return res.data
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async submit(
|
|
12
|
+
attemptId: string,
|
|
13
|
+
answers: Array<{ questionId: string; userAnswer: string | string[] | null }>,
|
|
14
|
+
timeUsedSeconds?: number,
|
|
15
|
+
): Promise<QuizAttempt> {
|
|
16
|
+
const res = await api.post<ApiResponse<QuizAttempt>>(
|
|
17
|
+
`/api/v1/quiz/attempts/${attemptId}/submit`,
|
|
18
|
+
{ answers, timeUsedSeconds },
|
|
19
|
+
)
|
|
20
|
+
return res.data
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async getDetail(attemptId: string): Promise<AttemptDetail> {
|
|
24
|
+
const res = await api.get<ApiResponse<AttemptDetail>>(`/api/v1/quiz/attempts/${attemptId}`)
|
|
25
|
+
return res.data
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async listByExam(examId: string, params: Record<string, string> = {}): Promise<PaginatedResponse<QuizAttempt>> {
|
|
29
|
+
const res = await api.get<ApiResponse<PaginatedResponse<QuizAttempt>>>(
|
|
30
|
+
`/api/v1/quiz/exams/${examId}/attempts`,
|
|
31
|
+
{ params },
|
|
32
|
+
)
|
|
33
|
+
return res.data
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async listMine(params: Record<string, string> = {}): Promise<PaginatedResponse<QuizAttempt>> {
|
|
37
|
+
const res = await api.get<ApiResponse<PaginatedResponse<QuizAttempt>>>('/api/v1/quiz/my/attempts', { params })
|
|
38
|
+
return res.data
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async gradeAnswer(answerId: string, score: number, feedback?: string): Promise<QuizAnswer> {
|
|
42
|
+
const res = await api.put<ApiResponse<QuizAnswer>>(
|
|
43
|
+
`/api/v1/quiz/answers/${answerId}/grade`,
|
|
44
|
+
{ score, feedback },
|
|
45
|
+
)
|
|
46
|
+
return res.data
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async finalizeGrading(attemptId: string): Promise<QuizAttempt> {
|
|
50
|
+
const res = await api.post<ApiResponse<QuizAttempt>>(`/api/v1/quiz/attempts/${attemptId}/finalize`, {})
|
|
51
|
+
return res.data
|
|
52
|
+
},
|
|
53
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
import type { QuizCategory } from '../types/quiz'
|
|
4
|
+
|
|
5
|
+
const BASE = '/api/v1/quiz/categories'
|
|
6
|
+
|
|
7
|
+
export const categoryApi = {
|
|
8
|
+
async list(): Promise<QuizCategory[]> {
|
|
9
|
+
const res = await api.get<ApiResponse<QuizCategory[]>>(BASE)
|
|
10
|
+
return res.data
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async create(data: Partial<QuizCategory>): Promise<QuizCategory> {
|
|
14
|
+
const res = await api.post<ApiResponse<QuizCategory>>(BASE, data)
|
|
15
|
+
return res.data
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async update(id: string, data: Partial<QuizCategory>): Promise<QuizCategory> {
|
|
19
|
+
const res = await api.put<ApiResponse<QuizCategory>>(`${BASE}/${id}`, data)
|
|
20
|
+
return res.data
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async delete(id: string): Promise<void> {
|
|
24
|
+
await api.delete(`${BASE}/${id}`)
|
|
25
|
+
},
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
import type { QuizExam, PaginatedResponse } from '../types/quiz'
|
|
4
|
+
|
|
5
|
+
const BASE = '/api/v1/quiz/exams'
|
|
6
|
+
|
|
7
|
+
export const examApi = {
|
|
8
|
+
async list(params: Record<string, string> = {}): Promise<PaginatedResponse<QuizExam>> {
|
|
9
|
+
const res = await api.get<ApiResponse<PaginatedResponse<QuizExam>>>(BASE, { params })
|
|
10
|
+
return res.data
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async get(id: string): Promise<QuizExam> {
|
|
14
|
+
const res = await api.get<ApiResponse<QuizExam>>(`${BASE}/${id}`)
|
|
15
|
+
return res.data
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async create(data: Partial<QuizExam>): Promise<QuizExam> {
|
|
19
|
+
const res = await api.post<ApiResponse<QuizExam>>(BASE, data)
|
|
20
|
+
return res.data
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async update(id: string, data: Partial<QuizExam>): Promise<QuizExam> {
|
|
24
|
+
const res = await api.put<ApiResponse<QuizExam>>(`${BASE}/${id}`, data)
|
|
25
|
+
return res.data
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async delete(id: string): Promise<void> {
|
|
29
|
+
await api.delete(`${BASE}/${id}`)
|
|
30
|
+
},
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
import type { QuizPaper, QuizPaperQuestion, PaperWithQuestions, PaginatedResponse } from '../types/quiz'
|
|
4
|
+
|
|
5
|
+
const BASE = '/api/v1/quiz/papers'
|
|
6
|
+
|
|
7
|
+
export const paperApi = {
|
|
8
|
+
async list(params: Record<string, string> = {}): Promise<PaginatedResponse<QuizPaper>> {
|
|
9
|
+
const res = await api.get<ApiResponse<PaginatedResponse<QuizPaper>>>(BASE, { params })
|
|
10
|
+
return res.data
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async get(id: string): Promise<QuizPaper> {
|
|
14
|
+
const res = await api.get<ApiResponse<QuizPaper>>(`${BASE}/${id}`)
|
|
15
|
+
return res.data
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async getWithQuestions(id: string): Promise<PaperWithQuestions> {
|
|
19
|
+
const res = await api.get<ApiResponse<PaperWithQuestions>>(`${BASE}/${id}/questions`)
|
|
20
|
+
return res.data
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async create(data: Partial<QuizPaper>): Promise<QuizPaper> {
|
|
24
|
+
const res = await api.post<ApiResponse<QuizPaper>>(BASE, data)
|
|
25
|
+
return res.data
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async update(id: string, data: Partial<QuizPaper>): Promise<QuizPaper> {
|
|
29
|
+
const res = await api.put<ApiResponse<QuizPaper>>(`${BASE}/${id}`, data)
|
|
30
|
+
return res.data
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async delete(id: string): Promise<void> {
|
|
34
|
+
await api.delete(`${BASE}/${id}`)
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async addQuestion(paperId: string, questionId: string, score?: number): Promise<QuizPaperQuestion> {
|
|
38
|
+
const res = await api.post<ApiResponse<QuizPaperQuestion>>(`${BASE}/${paperId}/questions`, { questionId, score })
|
|
39
|
+
return res.data
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async updateQuestion(paperId: string, questionId: string, data: Partial<QuizPaperQuestion>): Promise<QuizPaperQuestion> {
|
|
43
|
+
const res = await api.put<ApiResponse<QuizPaperQuestion>>(`${BASE}/${paperId}/questions/${questionId}`, data)
|
|
44
|
+
return res.data
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async removeQuestion(paperId: string, questionId: string): Promise<void> {
|
|
48
|
+
await api.delete(`${BASE}/${paperId}/questions/${questionId}`)
|
|
49
|
+
},
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
import type { QuizQuestion, PaginatedResponse, SmartGenerateRule } from '../types/quiz'
|
|
4
|
+
|
|
5
|
+
const BASE = '/api/v1/quiz/questions'
|
|
6
|
+
|
|
7
|
+
export const questionApi = {
|
|
8
|
+
async list(params: Record<string, string> = {}): Promise<PaginatedResponse<QuizQuestion>> {
|
|
9
|
+
const res = await api.get<ApiResponse<PaginatedResponse<QuizQuestion>>>(BASE, { params })
|
|
10
|
+
return res.data
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async get(id: string): Promise<QuizQuestion> {
|
|
14
|
+
const res = await api.get<ApiResponse<QuizQuestion>>(`${BASE}/${id}`)
|
|
15
|
+
return res.data
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async create(data: Partial<QuizQuestion>): Promise<QuizQuestion> {
|
|
19
|
+
const res = await api.post<ApiResponse<QuizQuestion>>(BASE, data)
|
|
20
|
+
return res.data
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async update(id: string, data: Partial<QuizQuestion>): Promise<QuizQuestion> {
|
|
24
|
+
const res = await api.put<ApiResponse<QuizQuestion>>(`${BASE}/${id}`, data)
|
|
25
|
+
return res.data
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async delete(id: string): Promise<void> {
|
|
29
|
+
await api.delete(`${BASE}/${id}`)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async smartGenerate(paperId: string, rules: SmartGenerateRule[]): Promise<{ added: number }> {
|
|
33
|
+
const res = await api.post<ApiResponse<{ added: number }>>(`${BASE}/smart-generate`, { paperId, rules })
|
|
34
|
+
return res.data
|
|
35
|
+
},
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
import type { ExamStats, UserStats, RankingEntry } from '../types/quiz'
|
|
4
|
+
|
|
5
|
+
export const statisticsApi = {
|
|
6
|
+
async getExamStats(examId: string): Promise<ExamStats> {
|
|
7
|
+
const res = await api.get<ApiResponse<ExamStats>>(`/api/v1/quiz/exams/${examId}/stats`)
|
|
8
|
+
return res.data
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async getRanking(examId: string, limit = 20): Promise<RankingEntry[]> {
|
|
12
|
+
const res = await api.get<ApiResponse<RankingEntry[]>>(
|
|
13
|
+
`/api/v1/quiz/exams/${examId}/ranking`,
|
|
14
|
+
{ params: { limit: String(limit) } },
|
|
15
|
+
)
|
|
16
|
+
return res.data
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async getMyStats(): Promise<UserStats> {
|
|
20
|
+
const res = await api.get<ApiResponse<UserStats>>('/api/v1/quiz/my/stats')
|
|
21
|
+
return res.data
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async getUserStats(userId: string): Promise<UserStats> {
|
|
25
|
+
const res = await api.get<ApiResponse<UserStats>>(`/api/v1/quiz/stats/user/${userId}`)
|
|
26
|
+
return res.data
|
|
27
|
+
},
|
|
28
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
|
|
3
|
+
interface QuizStoreState {
|
|
4
|
+
questionFilter: string
|
|
5
|
+
setQuestionFilter: (f: string) => void
|
|
6
|
+
paperFilter: string
|
|
7
|
+
setPaperFilter: (f: string) => void
|
|
8
|
+
examFilter: string
|
|
9
|
+
setExamFilter: (f: string) => void
|
|
10
|
+
currentAttemptId: string | null
|
|
11
|
+
setCurrentAttemptId: (id: string | null) => void
|
|
12
|
+
/** Saved in-progress answers keyed by questionId */
|
|
13
|
+
draftAnswers: Record<string, string | string[]>
|
|
14
|
+
setDraftAnswer: (questionId: string, answer: string | string[]) => void
|
|
15
|
+
clearDraftAnswers: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const useQuizStore = create<QuizStoreState>((set) => ({
|
|
19
|
+
questionFilter: 'all',
|
|
20
|
+
setQuestionFilter: (f) => set({ questionFilter: f }),
|
|
21
|
+
paperFilter: 'all',
|
|
22
|
+
setPaperFilter: (f) => set({ paperFilter: f }),
|
|
23
|
+
examFilter: 'all',
|
|
24
|
+
setExamFilter: (f) => set({ examFilter: f }),
|
|
25
|
+
currentAttemptId: null,
|
|
26
|
+
setCurrentAttemptId: (id) => set({ currentAttemptId: id }),
|
|
27
|
+
draftAnswers: {},
|
|
28
|
+
setDraftAnswer: (questionId, answer) =>
|
|
29
|
+
set((s) => ({ draftAnswers: { ...s.draftAnswers, [questionId]: answer } })),
|
|
30
|
+
clearDraftAnswers: () => set({ draftAnswers: {} }),
|
|
31
|
+
}))
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** @octo-cyber/quiz-engine — frontend types */
|
|
2
|
+
|
|
3
|
+
export interface QuizCategory {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
parentId: string | null
|
|
7
|
+
description: string | null
|
|
8
|
+
sortOrder: number
|
|
9
|
+
createdAt: string
|
|
10
|
+
updatedAt: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface QuizOption {
|
|
14
|
+
key: string
|
|
15
|
+
text: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type QuestionType = 'SINGLE_CHOICE' | 'MULTIPLE_CHOICE' | 'TRUE_FALSE' | 'FILL_BLANK' | 'ESSAY'
|
|
19
|
+
|
|
20
|
+
export interface QuizQuestion {
|
|
21
|
+
id: string
|
|
22
|
+
categoryId: string | null
|
|
23
|
+
categoryName?: string | null
|
|
24
|
+
type: QuestionType
|
|
25
|
+
content: string
|
|
26
|
+
options: QuizOption[] | null
|
|
27
|
+
correctAnswer: string | string[] | null
|
|
28
|
+
explanation: string | null
|
|
29
|
+
difficulty: number
|
|
30
|
+
tags: string[] | null
|
|
31
|
+
defaultScore: number
|
|
32
|
+
useCount: number
|
|
33
|
+
createdBy: string | null
|
|
34
|
+
createdAt: string
|
|
35
|
+
updatedAt: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type PaperStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'
|
|
39
|
+
|
|
40
|
+
export interface QuizPaper {
|
|
41
|
+
id: string
|
|
42
|
+
title: string
|
|
43
|
+
description: string | null
|
|
44
|
+
totalScore: number
|
|
45
|
+
passingScore: number
|
|
46
|
+
timeLimit: number
|
|
47
|
+
status: PaperStatus
|
|
48
|
+
createdBy: string | null
|
|
49
|
+
createdAt: string
|
|
50
|
+
updatedAt: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface QuizPaperQuestion {
|
|
54
|
+
id: string
|
|
55
|
+
paperId: string
|
|
56
|
+
questionId: string
|
|
57
|
+
score: number
|
|
58
|
+
sortOrder: number
|
|
59
|
+
sectionTitle: string | null
|
|
60
|
+
createdAt: string
|
|
61
|
+
questionDetail: QuizQuestion
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface PaperWithQuestions {
|
|
65
|
+
paper: QuizPaper
|
|
66
|
+
questions: QuizPaperQuestion[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ExamStatus = 'DRAFT' | 'SCHEDULED' | 'ONGOING' | 'ENDED' | 'CANCELLED'
|
|
70
|
+
|
|
71
|
+
export interface QuizExam {
|
|
72
|
+
id: string
|
|
73
|
+
title: string
|
|
74
|
+
paperId: string
|
|
75
|
+
description: string | null
|
|
76
|
+
startTime: string | null
|
|
77
|
+
endTime: string | null
|
|
78
|
+
maxAttempts: number
|
|
79
|
+
shuffleQuestions: boolean
|
|
80
|
+
shuffleOptions: boolean
|
|
81
|
+
showAnswerAfterSubmit: boolean
|
|
82
|
+
status: ExamStatus
|
|
83
|
+
createdBy: string | null
|
|
84
|
+
createdAt: string
|
|
85
|
+
updatedAt: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type AttemptStatus = 'IN_PROGRESS' | 'SUBMITTED' | 'GRADED'
|
|
89
|
+
|
|
90
|
+
export interface QuizAttempt {
|
|
91
|
+
id: string
|
|
92
|
+
examId: string
|
|
93
|
+
userId: string
|
|
94
|
+
startedAt: string
|
|
95
|
+
submittedAt: string | null
|
|
96
|
+
timeUsedSeconds: number
|
|
97
|
+
status: AttemptStatus
|
|
98
|
+
totalScore: number
|
|
99
|
+
isPassed: boolean
|
|
100
|
+
createdAt: string
|
|
101
|
+
updatedAt: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface QuizAnswer {
|
|
105
|
+
id: string
|
|
106
|
+
attemptId: string
|
|
107
|
+
questionId: string
|
|
108
|
+
userAnswer: string | string[] | null
|
|
109
|
+
isCorrect: boolean | null
|
|
110
|
+
score: number
|
|
111
|
+
feedback: string | null
|
|
112
|
+
gradedBy: string | null
|
|
113
|
+
gradedAt: string | null
|
|
114
|
+
createdAt: string
|
|
115
|
+
updatedAt: string
|
|
116
|
+
questionDetail?: QuizQuestion
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface AttemptDetail {
|
|
120
|
+
attempt: QuizAttempt
|
|
121
|
+
answers: Array<QuizAnswer & { questionDetail: QuizQuestion }>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface PaginatedResponse<T> {
|
|
125
|
+
items: T[]
|
|
126
|
+
total: number
|
|
127
|
+
page: number
|
|
128
|
+
pageSize: number
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ExamStats {
|
|
132
|
+
examId: string
|
|
133
|
+
title: string
|
|
134
|
+
totalAttempts: number
|
|
135
|
+
submittedAttempts: number
|
|
136
|
+
gradedAttempts: number
|
|
137
|
+
averageScore: number
|
|
138
|
+
highestScore: number
|
|
139
|
+
lowestScore: number
|
|
140
|
+
passRate: number
|
|
141
|
+
scoreDistribution: Array<{ range: string; count: number }>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface UserStats {
|
|
145
|
+
userId: string
|
|
146
|
+
totalAttempts: number
|
|
147
|
+
averageScore: number
|
|
148
|
+
highestScore: number
|
|
149
|
+
passCount: number
|
|
150
|
+
passRate: number
|
|
151
|
+
recentAttempts: QuizAttempt[]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface RankingEntry {
|
|
155
|
+
userId: string
|
|
156
|
+
score: number
|
|
157
|
+
rank: number
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface SmartGenerateRule {
|
|
161
|
+
categoryId?: string | null
|
|
162
|
+
type?: QuestionType
|
|
163
|
+
difficulty?: number
|
|
164
|
+
count: number
|
|
165
|
+
scorePerQuestion?: number
|
|
166
|
+
}
|