@hydralms/components 0.2.0 → 0.3.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/StudentProfile-BVfZMbnV.cjs +1 -0
- package/dist/StudentProfile-DeMxdrL3.js +3275 -0
- package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
- package/dist/assessment-toolbar/timer-display.d.ts +1 -1
- package/dist/common/index.d.ts +2 -1
- package/dist/common/pagination.d.ts +26 -0
- package/dist/common/types.d.ts +1 -0
- package/dist/components.css +1 -1
- package/dist/content/audio-player.d.ts +22 -0
- package/dist/content/code-block.d.ts +30 -0
- package/dist/content/embed-block.d.ts +28 -0
- package/dist/content/index.d.ts +6 -0
- package/dist/content/types.d.ts +24 -0
- package/dist/curriculum/course-card.d.ts +51 -0
- package/dist/curriculum/index.d.ts +2 -0
- package/dist/curriculum/types.d.ts +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +494 -444
- package/dist/license/HydraContext.d.ts +16 -0
- package/dist/license/ProBadge.d.ts +6 -0
- package/dist/license/index.d.ts +7 -0
- package/dist/license/tiers.d.ts +3 -0
- package/dist/license/useHydraLicense.d.ts +6 -0
- package/dist/license/validate.d.ts +13 -0
- package/dist/license/withProGate.d.ts +6 -0
- package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
- package/dist/modules/AssignmentModule/types.d.ts +5 -1
- package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
- package/dist/modules/CertificateModule/types.d.ts +6 -4
- package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
- package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
- package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
- package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
- package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
- package/dist/modules/ExamModule/types.d.ts +5 -14
- package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
- package/dist/modules/FlashcardLab/types.d.ts +2 -0
- package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
- package/dist/modules/GradeCenterModule/types.d.ts +2 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
- package/dist/modules/QuizModule/types.d.ts +5 -14
- package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
- package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
- package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
- package/dist/modules/StudentProfileModule/types.d.ts +43 -0
- package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
- package/dist/modules/SurveyModule/types.d.ts +2 -0
- package/dist/modules/_shared/assessment-intro.d.ts +16 -0
- package/dist/modules/_shared/assessment-results.d.ts +23 -0
- package/dist/modules/_shared/types.d.ts +10 -0
- package/dist/modules/_shared/use-timer.d.ts +9 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules.cjs +1 -1
- package/dist/modules.js +1266 -854
- package/dist/progress/types.d.ts +2 -0
- package/dist/provider/HydraProvider.d.ts +5 -1
- package/dist/questions/choice.d.ts +1 -1
- package/dist/questions/confidence-indicator.d.ts +37 -0
- package/dist/questions/essay.d.ts +1 -1
- package/dist/questions/fill-in-the-blank.d.ts +1 -1
- package/dist/questions/hotspot.d.ts +1 -1
- package/dist/questions/index.d.ts +2 -0
- package/dist/questions/inline-choice.d.ts +1 -1
- package/dist/questions/matching.d.ts +1 -1
- package/dist/questions/multiple-choice.d.ts +1 -1
- package/dist/questions/numeric.d.ts +1 -1
- package/dist/questions/ordering.d.ts +1 -1
- package/dist/questions/question-renderer.d.ts +1 -1
- package/dist/questions/scenario.d.ts +1 -1
- package/dist/questions/spreadsheet.d.ts +1 -1
- package/dist/questions/true-false.d.ts +1 -1
- package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
- package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
- package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
- package/dist/sections/AssessmentReview/types.d.ts +6 -0
- package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
- package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
- package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
- package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
- package/dist/sections/CertificateViewer/types.d.ts +6 -0
- package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
- package/dist/sections/CourseCatalog/types.d.ts +80 -0
- package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
- package/dist/sections/CourseOutline/types.d.ts +6 -0
- package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
- package/dist/sections/DiscussionThread/types.d.ts +6 -0
- package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
- package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
- package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
- package/dist/sections/ExamSession/types.d.ts +6 -0
- package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
- package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
- package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
- package/dist/sections/ForumBoard/types.d.ts +14 -0
- package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
- package/dist/sections/GradebookTable/types.d.ts +14 -0
- package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
- package/dist/sections/LecturePlayer/types.d.ts +8 -0
- package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
- package/dist/sections/LessonPage/types.d.ts +6 -0
- package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
- package/dist/sections/PracticeQuiz/types.d.ts +6 -0
- package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
- package/dist/sections/ProgressDashboard/types.d.ts +6 -0
- package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
- package/dist/sections/QuizSession/types.d.ts +6 -0
- package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
- package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
- package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
- package/dist/sections/ResourceLibrary/types.d.ts +15 -1
- package/dist/sections/RubricView/RubricView.d.ts +1 -1
- package/dist/sections/RubricView/types.d.ts +6 -0
- package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
- package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
- package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
- package/dist/sections/StudentProfile/types.d.ts +98 -0
- package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
- package/dist/sections/SurveyForm/types.d.ts +6 -0
- package/dist/sections/_shared/merge-answers.d.ts +9 -0
- package/dist/sections/_shared/section-shell.d.ts +20 -0
- package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
- package/dist/sections/index.d.ts +6 -0
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +268 -307
- package/dist/tabs-BsfVo2Bl.cjs +173 -0
- package/dist/{tabs-Wf3h_Cx3.js → tabs-BuY1iNJE.js} +7532 -6807
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/rich-text-editor.d.ts +3 -1
- package/dist/ui/toast.d.ts +43 -0
- package/dist/utils/debounce.d.ts +5 -1
- package/dist/utils/pick-palette-color.d.ts +19 -0
- package/dist/video/types.d.ts +15 -0
- package/dist/video/video-player.d.ts +1 -1
- package/dist/withProGate-BWqcKdPM.js +137 -0
- package/dist/withProGate-DX6XqKLp.cjs +1 -0
- package/package.json +34 -220
- package/src/assessment-toolbar/question-navigator.tsx +10 -5
- package/src/assessment-toolbar/timer-display.tsx +4 -3
- package/src/assessment-toolbar/use-countdown.ts +1 -1
- package/src/common/empty-state.tsx +1 -0
- package/src/common/index.ts +2 -0
- package/src/common/pagination.tsx +135 -0
- package/src/common/search-input.tsx +2 -1
- package/src/common/types.ts +2 -0
- package/src/content/attachment-list.tsx +2 -0
- package/src/content/audio-player.tsx +196 -0
- package/src/content/code-block.tsx +113 -0
- package/src/content/content-block.tsx +64 -0
- package/src/content/embed-block.tsx +78 -0
- package/src/content/file-upload-zone.tsx +10 -0
- package/src/content/index.ts +6 -0
- package/src/content/types.ts +5 -0
- package/src/curriculum/course-card.tsx +199 -0
- package/src/curriculum/curriculum-item.tsx +3 -3
- package/src/curriculum/curriculum-tree.tsx +20 -13
- package/src/curriculum/index.ts +2 -0
- package/src/curriculum/types.ts +2 -2
- package/src/flashcards/flashcard.tsx +28 -8
- package/src/index.ts +3 -0
- package/src/license/HydraContext.tsx +62 -0
- package/src/license/ProBadge.tsx +43 -0
- package/src/license/index.ts +7 -0
- package/src/license/tiers.ts +24 -0
- package/src/license/useHydraLicense.ts +10 -0
- package/src/license/validate.ts +90 -0
- package/src/license/withProGate.tsx +21 -0
- package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
- package/src/modules/AssignmentModule/types.ts +5 -1
- package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
- package/src/modules/CertificateModule/types.ts +6 -4
- package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
- package/src/modules/CourseCatalogModule/types.ts +47 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +37 -22
- package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
- package/src/modules/ExamModule/ExamModule.tsx +64 -198
- package/src/modules/ExamModule/types.ts +5 -14
- package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
- package/src/modules/FlashcardLab/types.ts +2 -0
- package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
- package/src/modules/GradeCenterModule/types.ts +2 -0
- package/src/modules/QuizModule/QuizModule.tsx +49 -169
- package/src/modules/QuizModule/types.ts +5 -15
- package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
- package/src/modules/StudentDashboardModule/types.ts +56 -0
- package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
- package/src/modules/StudentProfileModule/types.ts +45 -0
- package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
- package/src/modules/SurveyModule/types.ts +2 -0
- package/src/modules/_shared/assessment-intro.tsx +75 -0
- package/src/modules/_shared/assessment-results.tsx +133 -0
- package/src/modules/_shared/types.ts +11 -0
- package/src/modules/_shared/use-timer.ts +49 -0
- package/src/modules/index.ts +9 -0
- package/src/progress/achievement-badge.tsx +3 -3
- package/src/progress/grade-indicator.tsx +9 -1
- package/src/progress/progress-ring.tsx +2 -1
- package/src/progress/stat-card.tsx +8 -1
- package/src/progress/types.ts +2 -0
- package/src/provider/HydraProvider.tsx +15 -6
- package/src/questions/choice.tsx +13 -6
- package/src/questions/confidence-indicator.tsx +107 -0
- package/src/questions/essay.tsx +6 -4
- package/src/questions/fill-in-the-blank.tsx +8 -4
- package/src/questions/hotspot.tsx +4 -4
- package/src/questions/index.ts +2 -0
- package/src/questions/inline-choice.tsx +5 -4
- package/src/questions/matching.tsx +5 -4
- package/src/questions/multiple-choice.tsx +13 -6
- package/src/questions/numeric.tsx +8 -4
- package/src/questions/ordering.tsx +12 -4
- package/src/questions/question-renderer.tsx +3 -2
- package/src/questions/scenario.tsx +4 -4
- package/src/questions/spreadsheet.tsx +5 -4
- package/src/questions/true-false.tsx +13 -6
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
- package/src/sections/AnnouncementFeed/types.ts +15 -1
- package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
- package/src/sections/AssessmentReview/types.ts +6 -0
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
- package/src/sections/AssignmentSubmission/types.ts +6 -0
- package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
- package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
- package/src/sections/CertificateViewer/types.ts +6 -0
- package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
- package/src/sections/CourseCatalog/types.ts +76 -0
- package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
- package/src/sections/CourseOutline/types.ts +6 -0
- package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
- package/src/sections/DiscussionThread/types.ts +6 -0
- package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
- package/src/sections/EnrollmentWizard/types.ts +65 -0
- package/src/sections/ExamSession/ExamSession.tsx +100 -94
- package/src/sections/ExamSession/types.ts +6 -0
- package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
- package/src/sections/FlashcardStudySession/types.ts +6 -0
- package/src/sections/ForumBoard/ForumBoard.tsx +59 -1
- package/src/sections/ForumBoard/types.ts +14 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
- package/src/sections/GradebookTable/types.ts +14 -0
- package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
- package/src/sections/LecturePlayer/types.ts +8 -0
- package/src/sections/LessonPage/LessonPage.tsx +36 -5
- package/src/sections/LessonPage/types.ts +6 -0
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
- package/src/sections/PracticeQuiz/types.ts +6 -0
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
- package/src/sections/ProgressDashboard/types.ts +6 -0
- package/src/sections/QuizSession/QuizSession.tsx +71 -82
- package/src/sections/QuizSession/types.ts +6 -0
- package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
- package/src/sections/RequirementsChecklist/types.ts +6 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
- package/src/sections/ResourceLibrary/types.ts +15 -1
- package/src/sections/RubricView/RubricView.tsx +37 -1
- package/src/sections/RubricView/types.ts +6 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
- package/src/sections/ScrollableQuiz/types.ts +6 -0
- package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
- package/src/sections/StudentProfile/types.ts +99 -0
- package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
- package/src/sections/SurveyForm/types.ts +6 -0
- package/src/sections/_shared/merge-answers.ts +22 -0
- package/src/sections/_shared/section-shell.tsx +64 -0
- package/src/sections/_shared/use-assessment-session.ts +125 -0
- package/src/sections/index.ts +22 -0
- package/src/social/user-avatar.tsx +9 -5
- package/src/styles/globals.css +39 -41
- package/src/ui/badge.tsx +8 -0
- package/src/ui/index.ts +2 -0
- package/src/ui/progress.tsx +4 -0
- package/src/ui/rich-text-editor.tsx +10 -0
- package/src/ui/rich-text-toolbar.tsx +2 -1
- package/src/ui/toast.tsx +170 -0
- package/src/utils/debounce.ts +8 -2
- package/src/utils/pick-palette-color.ts +33 -0
- package/src/video/types.ts +16 -0
- package/src/video/video-player.tsx +13 -1
- package/dist/ForumBoard-CHXU3mjC.js +0 -2207
- package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
- package/dist/tabs-DRM2Iq_J.cjs +0 -172
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Play,
|
|
4
|
-
RotateCcw,
|
|
5
|
-
Clock,
|
|
6
|
-
HelpCircle,
|
|
7
|
-
CheckCircle2,
|
|
8
|
-
XCircle,
|
|
9
|
-
Trophy,
|
|
10
|
-
} from "lucide-react";
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
2
|
+
import { Trophy } from "lucide-react";
|
|
11
3
|
import { QuizSession } from "../../sections/QuizSession/QuizSession";
|
|
12
|
-
import { AssessmentReview } from "../../sections/AssessmentReview/AssessmentReview";
|
|
13
|
-
import { ProgressRing } from "../../progress/progress-ring";
|
|
14
|
-
import { StatCard } from "../../progress/stat-card";
|
|
15
|
-
import { Button } from "../../ui/button";
|
|
16
|
-
import { Badge } from "../../ui/badge";
|
|
17
|
-
import { Card, CardContent } from "../../ui/card";
|
|
18
|
-
import { Separator } from "../../ui/separator";
|
|
19
|
-
import { formatDuration } from "../../utils/format-duration";
|
|
20
4
|
import { cn } from "../../lib/utils";
|
|
21
5
|
import type { SessionAnswer } from "../../questions/types";
|
|
22
6
|
import { scoreAssessment } from "../../questions/scoring";
|
|
7
|
+
import { useTimer } from "../_shared/use-timer";
|
|
8
|
+
import { AssessmentIntro } from "../_shared/assessment-intro";
|
|
9
|
+
import { AssessmentResults } from "../_shared/assessment-results";
|
|
10
|
+
import { withProGate } from "../../license/withProGate";
|
|
23
11
|
import type { QuizModuleProps, QuizModuleResult } from "./types";
|
|
24
12
|
|
|
25
13
|
type InternalStep =
|
|
@@ -27,7 +15,7 @@ type InternalStep =
|
|
|
27
15
|
| { tag: "quiz" }
|
|
28
16
|
| { tag: "results"; result: QuizModuleResult };
|
|
29
17
|
|
|
30
|
-
|
|
18
|
+
function QuizModuleBase({
|
|
31
19
|
title,
|
|
32
20
|
description,
|
|
33
21
|
questions,
|
|
@@ -37,106 +25,55 @@ export function QuizModule({
|
|
|
37
25
|
onComplete,
|
|
38
26
|
showReview = true,
|
|
39
27
|
questionMaterials,
|
|
28
|
+
readOnly = false,
|
|
40
29
|
className,
|
|
41
30
|
style,
|
|
42
31
|
}: QuizModuleProps) {
|
|
43
32
|
const [step, setStep] = useState<InternalStep>({ tag: "intro" });
|
|
44
|
-
const [timeElapsed, setTimeElapsed] = useState(0);
|
|
45
|
-
const startTimeRef = useRef<number | null>(null);
|
|
46
|
-
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
47
33
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
34
|
+
const { timeElapsed, getFinalElapsed, reset: resetTimer } = useTimer(step.tag === "quiz");
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}, [step.tag]);
|
|
36
|
+
const onCompleteRef = useRef(onComplete);
|
|
37
|
+
onCompleteRef.current = onComplete;
|
|
52
38
|
|
|
53
|
-
// Timer for quiz step
|
|
54
39
|
useEffect(() => {
|
|
55
|
-
|
|
56
|
-
startTimeRef.current = Date.now();
|
|
57
|
-
intervalRef.current = setInterval(() => {
|
|
58
|
-
if (startTimeRef.current) {
|
|
59
|
-
setTimeElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
|
|
60
|
-
}
|
|
61
|
-
}, 1000);
|
|
62
|
-
} else {
|
|
63
|
-
if (intervalRef.current) {
|
|
64
|
-
clearInterval(intervalRef.current);
|
|
65
|
-
intervalRef.current = null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return () => {
|
|
69
|
-
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
70
|
-
};
|
|
40
|
+
contentRef.current?.focus({ preventScroll: true });
|
|
71
41
|
}, [step.tag]);
|
|
72
42
|
|
|
73
|
-
|
|
43
|
+
const handleSubmit = useCallback((answers: SessionAnswer[]) => {
|
|
74
44
|
const { correct, total, percentage } = scoreAssessment(questions, answers);
|
|
75
|
-
const
|
|
76
|
-
? Math.floor((Date.now() - startTimeRef.current) / 1000)
|
|
77
|
-
: timeElapsed;
|
|
78
|
-
|
|
79
|
-
return {
|
|
45
|
+
const result: QuizModuleResult = {
|
|
80
46
|
answers,
|
|
81
47
|
correct,
|
|
82
48
|
total,
|
|
83
49
|
percentage,
|
|
84
50
|
passed: passingScore !== undefined ? percentage >= passingScore : true,
|
|
85
|
-
timeElapsedSeconds:
|
|
51
|
+
timeElapsedSeconds: getFinalElapsed(),
|
|
86
52
|
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function handleSubmit(answers: SessionAnswer[]) {
|
|
90
|
-
const result = scoreAnswers(answers);
|
|
91
53
|
setStep({ tag: "results", result });
|
|
92
|
-
|
|
93
|
-
}
|
|
54
|
+
onCompleteRef.current?.(result);
|
|
55
|
+
}, [questions, passingScore, getFinalElapsed]);
|
|
94
56
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
startTimeRef.current = null;
|
|
57
|
+
const handleRetake = useCallback(() => {
|
|
58
|
+
resetTimer();
|
|
98
59
|
setStep({ tag: "intro" });
|
|
99
|
-
}
|
|
60
|
+
}, [resetTimer]);
|
|
100
61
|
|
|
101
62
|
// ─── Intro Screen ───
|
|
102
63
|
if (step.tag === "intro") {
|
|
103
64
|
return (
|
|
104
65
|
<div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<div className="flex flex-wrap justify-center gap-2 mb-8">
|
|
117
|
-
<Badge variant="outline" className="gap-1.5">
|
|
118
|
-
<HelpCircle className="size-3.5" />
|
|
119
|
-
{questions.length} questions
|
|
120
|
-
</Badge>
|
|
121
|
-
{timeLimitSeconds && (
|
|
122
|
-
<Badge variant="outline" className="gap-1.5">
|
|
123
|
-
<Clock className="size-3.5" />
|
|
124
|
-
{formatDuration(timeLimitSeconds)} time limit
|
|
125
|
-
</Badge>
|
|
126
|
-
)}
|
|
127
|
-
{passingScore !== undefined && (
|
|
128
|
-
<Badge variant="outline" className="gap-1.5">
|
|
129
|
-
<CheckCircle2 className="size-3.5" />
|
|
130
|
-
{passingScore}% to pass
|
|
131
|
-
</Badge>
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
134
|
-
<Button size="lg" onClick={() => setStep({ tag: "quiz" })}>
|
|
135
|
-
<Play className="size-4 mr-2" />
|
|
136
|
-
Start Quiz
|
|
137
|
-
</Button>
|
|
138
|
-
</CardContent>
|
|
139
|
-
</Card>
|
|
66
|
+
<AssessmentIntro
|
|
67
|
+
icon={<Trophy className="size-7 text-primary" />}
|
|
68
|
+
title={title}
|
|
69
|
+
description={description}
|
|
70
|
+
questionCount={questions.length}
|
|
71
|
+
timeLimitSeconds={timeLimitSeconds}
|
|
72
|
+
passingScore={passingScore}
|
|
73
|
+
startLabel="Start Quiz"
|
|
74
|
+
onStart={() => setStep({ tag: "quiz" })}
|
|
75
|
+
readOnly={readOnly}
|
|
76
|
+
/>
|
|
140
77
|
</div>
|
|
141
78
|
);
|
|
142
79
|
}
|
|
@@ -151,6 +88,7 @@ export function QuizModule({
|
|
|
151
88
|
timeElapsedSeconds={timeElapsed}
|
|
152
89
|
timeLimitSeconds={timeLimitSeconds}
|
|
153
90
|
questionMaterials={questionMaterials}
|
|
91
|
+
readOnly={readOnly}
|
|
154
92
|
/>
|
|
155
93
|
</div>
|
|
156
94
|
);
|
|
@@ -158,84 +96,26 @@ export function QuizModule({
|
|
|
158
96
|
|
|
159
97
|
// ─── Results Screen ───
|
|
160
98
|
const { result } = step;
|
|
161
|
-
const passed = result.passed;
|
|
162
99
|
|
|
163
100
|
return (
|
|
164
101
|
<div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
{passed ? "Passed" : "Failed"}
|
|
181
|
-
</Badge>
|
|
182
|
-
<h2 className="text-xl font-bold text-foreground">{title}</h2>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
{/* Stats grid */}
|
|
186
|
-
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
|
|
187
|
-
<StatCard
|
|
188
|
-
icon={<CheckCircle2 />}
|
|
189
|
-
label="Correct"
|
|
190
|
-
description="Questions answered right"
|
|
191
|
-
value={`${result.correct}/${result.total}`}
|
|
192
|
-
/>
|
|
193
|
-
<StatCard
|
|
194
|
-
icon={<XCircle />}
|
|
195
|
-
label="Incorrect"
|
|
196
|
-
description="Questions to review"
|
|
197
|
-
value={`${result.total - result.correct}/${result.total}`}
|
|
198
|
-
/>
|
|
199
|
-
<StatCard
|
|
200
|
-
icon={<Trophy />}
|
|
201
|
-
label="Score"
|
|
202
|
-
description="Overall percentage"
|
|
203
|
-
value={`${result.percentage}%`}
|
|
204
|
-
/>
|
|
205
|
-
<StatCard
|
|
206
|
-
icon={<Clock />}
|
|
207
|
-
label="Time"
|
|
208
|
-
description="Total elapsed"
|
|
209
|
-
value={formatDuration(result.timeElapsedSeconds)}
|
|
210
|
-
/>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
{/* Actions */}
|
|
214
|
-
{allowRetake && (
|
|
215
|
-
<div className="flex justify-center mb-8">
|
|
216
|
-
<Button variant="outline" onClick={handleRetake}>
|
|
217
|
-
<RotateCcw className="size-4 mr-2" />
|
|
218
|
-
Retake Quiz
|
|
219
|
-
</Button>
|
|
220
|
-
</div>
|
|
221
|
-
)}
|
|
222
|
-
</CardContent>
|
|
223
|
-
</Card>
|
|
224
|
-
|
|
225
|
-
{/* Per-question review */}
|
|
226
|
-
{showReview && (
|
|
227
|
-
<>
|
|
228
|
-
<Separator className="my-6" />
|
|
229
|
-
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
230
|
-
Question Review
|
|
231
|
-
</h3>
|
|
232
|
-
<AssessmentReview
|
|
233
|
-
questions={questions}
|
|
234
|
-
sessionAnswers={result.answers}
|
|
235
|
-
showCorrectAnswers
|
|
236
|
-
/>
|
|
237
|
-
</>
|
|
238
|
-
)}
|
|
102
|
+
<AssessmentResults
|
|
103
|
+
title={title}
|
|
104
|
+
percentage={result.percentage}
|
|
105
|
+
passed={result.passed}
|
|
106
|
+
correct={result.correct}
|
|
107
|
+
total={result.total}
|
|
108
|
+
timeElapsedSeconds={result.timeElapsedSeconds}
|
|
109
|
+
answers={result.answers}
|
|
110
|
+
questions={questions}
|
|
111
|
+
allowRetake={allowRetake}
|
|
112
|
+
onRetake={handleRetake}
|
|
113
|
+
retakeLabel="Retake Quiz"
|
|
114
|
+
showReview={showReview}
|
|
115
|
+
readOnly={readOnly}
|
|
116
|
+
/>
|
|
239
117
|
</div>
|
|
240
118
|
);
|
|
241
119
|
}
|
|
120
|
+
|
|
121
|
+
export const QuizModule = withProGate(QuizModuleBase, "QuizModule");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { QuestionData, QuestionMaterial
|
|
1
|
+
import type { QuestionData, QuestionMaterial } from "../../questions/types";
|
|
2
|
+
import type { AssessmentResult } from "../_shared/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* QuizModule — a complete multi-step assessment experience.
|
|
@@ -39,23 +40,12 @@ export interface QuizModuleProps {
|
|
|
39
40
|
* button appears in the question header, opening a drawer with content blocks.
|
|
40
41
|
*/
|
|
41
42
|
questionMaterials?: QuestionMaterial[];
|
|
43
|
+
/** When true, disables interactions for preview/demo mode. @default false */
|
|
44
|
+
readOnly?: boolean;
|
|
42
45
|
/** CSS class name for the root element */
|
|
43
46
|
className?: string;
|
|
44
47
|
/** Inline styles for the root element */
|
|
45
48
|
style?: React.CSSProperties;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
export interface QuizModuleResult {
|
|
49
|
-
/** The user's submitted answers */
|
|
50
|
-
answers: SessionAnswer[];
|
|
51
|
-
/** Number of correct answers */
|
|
52
|
-
correct: number;
|
|
53
|
-
/** Total number of questions */
|
|
54
|
-
total: number;
|
|
55
|
-
/** Score as a percentage (0-100) */
|
|
56
|
-
percentage: number;
|
|
57
|
-
/** Whether the user passed (only meaningful when passingScore is set) */
|
|
58
|
-
passed: boolean;
|
|
59
|
-
/** Total time taken in seconds */
|
|
60
|
-
timeElapsedSeconds: number;
|
|
61
|
-
}
|
|
51
|
+
export interface QuizModuleResult extends AssessmentResult {}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
|
+
import { BookOpen, Bell, BarChart3 } from "lucide-react";
|
|
3
|
+
import { ProgressDashboard } from "../../sections/ProgressDashboard/ProgressDashboard";
|
|
4
|
+
import { CourseCatalog } from "../../sections/CourseCatalog/CourseCatalog";
|
|
5
|
+
import { AnnouncementFeed } from "../../sections/AnnouncementFeed/AnnouncementFeed";
|
|
6
|
+
import { ProgressRing } from "../../progress/progress-ring";
|
|
7
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
|
|
8
|
+
import { Separator } from "../../ui/separator";
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
import { withProGate } from "../../license/withProGate";
|
|
11
|
+
import type { StudentDashboardModuleProps } from "./types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* StudentDashboardModule — a multi-tab student home page.
|
|
15
|
+
*
|
|
16
|
+
* Combines ProgressDashboard, CourseCatalog, and AnnouncementFeed in a tabbed layout
|
|
17
|
+
* to provide a complete student landing page experience.
|
|
18
|
+
*/
|
|
19
|
+
function StudentDashboardModuleBase({
|
|
20
|
+
studentName,
|
|
21
|
+
overallProgress,
|
|
22
|
+
totalTimeSpent,
|
|
23
|
+
modules,
|
|
24
|
+
recentActivity,
|
|
25
|
+
streak,
|
|
26
|
+
achievements,
|
|
27
|
+
enrolledCourses,
|
|
28
|
+
onCourseClick,
|
|
29
|
+
announcements = [],
|
|
30
|
+
onAnnouncementSelect,
|
|
31
|
+
onMarkRead,
|
|
32
|
+
readOnly = false,
|
|
33
|
+
className,
|
|
34
|
+
style,
|
|
35
|
+
}: StudentDashboardModuleProps) {
|
|
36
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
contentRef.current?.focus({ preventScroll: true });
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const unreadCount = announcements.filter((a) => !a.isRead).length;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
ref={contentRef}
|
|
47
|
+
tabIndex={-1}
|
|
48
|
+
className={cn("outline-none", className)}
|
|
49
|
+
style={style}
|
|
50
|
+
>
|
|
51
|
+
{/* Header */}
|
|
52
|
+
<div className="flex items-center justify-between mb-6">
|
|
53
|
+
<h2 className="text-xl font-bold text-foreground">
|
|
54
|
+
Welcome back, {studentName}
|
|
55
|
+
</h2>
|
|
56
|
+
<ProgressRing value={overallProgress} size={48} strokeWidth={4} />
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<Separator className="mb-6" />
|
|
60
|
+
|
|
61
|
+
{/* Tabs */}
|
|
62
|
+
<Tabs defaultValue="overview">
|
|
63
|
+
<TabsList className="mb-6">
|
|
64
|
+
<TabsTrigger value="overview">
|
|
65
|
+
<BarChart3 className="size-4 mr-1.5" />
|
|
66
|
+
Overview
|
|
67
|
+
</TabsTrigger>
|
|
68
|
+
<TabsTrigger value="courses">
|
|
69
|
+
<BookOpen className="size-4 mr-1.5" />
|
|
70
|
+
My Courses
|
|
71
|
+
</TabsTrigger>
|
|
72
|
+
<TabsTrigger value="announcements">
|
|
73
|
+
<Bell className="size-4 mr-1.5" />
|
|
74
|
+
Announcements
|
|
75
|
+
{unreadCount > 0 && (
|
|
76
|
+
<span className="ml-1.5 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-medium min-w-5 h-5 px-1.5">
|
|
77
|
+
{unreadCount}
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
80
|
+
</TabsTrigger>
|
|
81
|
+
</TabsList>
|
|
82
|
+
|
|
83
|
+
<TabsContent value="overview">
|
|
84
|
+
<ProgressDashboard
|
|
85
|
+
overallProgress={overallProgress}
|
|
86
|
+
totalTimeSpent={totalTimeSpent}
|
|
87
|
+
modules={modules}
|
|
88
|
+
recentActivity={recentActivity}
|
|
89
|
+
streak={streak}
|
|
90
|
+
achievements={achievements}
|
|
91
|
+
/>
|
|
92
|
+
</TabsContent>
|
|
93
|
+
|
|
94
|
+
<TabsContent value="courses">
|
|
95
|
+
<CourseCatalog
|
|
96
|
+
courses={enrolledCourses}
|
|
97
|
+
onCourseClick={(course) => !readOnly && onCourseClick(course.uid)}
|
|
98
|
+
showSearch
|
|
99
|
+
viewMode="grid"
|
|
100
|
+
readOnly={readOnly}
|
|
101
|
+
/>
|
|
102
|
+
</TabsContent>
|
|
103
|
+
|
|
104
|
+
<TabsContent value="announcements">
|
|
105
|
+
<AnnouncementFeed
|
|
106
|
+
announcements={announcements}
|
|
107
|
+
onSelect={onAnnouncementSelect}
|
|
108
|
+
onMarkRead={onMarkRead}
|
|
109
|
+
readOnly={readOnly}
|
|
110
|
+
/>
|
|
111
|
+
</TabsContent>
|
|
112
|
+
</Tabs>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const StudentDashboardModule = withProGate(StudentDashboardModuleBase, "StudentDashboardModule");
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CourseInfo } from "../../sections/CourseCatalog/types";
|
|
2
|
+
import type { Announcement } from "../../sections/AnnouncementFeed/types";
|
|
3
|
+
import type {
|
|
4
|
+
ModuleProgress,
|
|
5
|
+
ActivityItem,
|
|
6
|
+
Achievement,
|
|
7
|
+
} from "../../sections/ProgressDashboard/types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* StudentDashboardModule — a multi-tab student home page.
|
|
11
|
+
*
|
|
12
|
+
* Combines ProgressDashboard, CourseCatalog, and AnnouncementFeed in a tabbed layout
|
|
13
|
+
* to provide a complete student landing page experience.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <StudentDashboardModule
|
|
17
|
+
* studentName="Jane"
|
|
18
|
+
* overallProgress={72}
|
|
19
|
+
* totalTimeSpent={14400}
|
|
20
|
+
* modules={[]}
|
|
21
|
+
* enrolledCourses={courses}
|
|
22
|
+
* onCourseClick={(uid) => navigate(`/courses/${uid}`)}
|
|
23
|
+
* />
|
|
24
|
+
*/
|
|
25
|
+
export interface StudentDashboardModuleProps {
|
|
26
|
+
/** Student's display name */
|
|
27
|
+
studentName: string;
|
|
28
|
+
/** Overall progress percentage (0-100) */
|
|
29
|
+
overallProgress: number;
|
|
30
|
+
/** Total time spent in seconds */
|
|
31
|
+
totalTimeSpent: number;
|
|
32
|
+
/** Per-module progress data */
|
|
33
|
+
modules: ModuleProgress[];
|
|
34
|
+
/** Recent activity items */
|
|
35
|
+
recentActivity?: ActivityItem[];
|
|
36
|
+
/** Streak data */
|
|
37
|
+
streak?: { currentDays: number; longestDays: number };
|
|
38
|
+
/** Achievements */
|
|
39
|
+
achievements?: Achievement[];
|
|
40
|
+
/** Enrolled courses for the courses tab */
|
|
41
|
+
enrolledCourses: CourseInfo[];
|
|
42
|
+
/** Called when a course is clicked */
|
|
43
|
+
onCourseClick: (courseUid: string) => void;
|
|
44
|
+
/** Announcements for the announcements tab */
|
|
45
|
+
announcements?: Announcement[];
|
|
46
|
+
/** Called when an announcement is selected */
|
|
47
|
+
onAnnouncementSelect?: (announcement: Announcement) => void;
|
|
48
|
+
/** Called when an announcement is marked as read */
|
|
49
|
+
onMarkRead?: (uid: string) => void;
|
|
50
|
+
/** When true, disables interactions for preview/demo mode. @default false */
|
|
51
|
+
readOnly?: boolean;
|
|
52
|
+
/** CSS class name for the root element */
|
|
53
|
+
className?: string;
|
|
54
|
+
/** Inline styles for the root element */
|
|
55
|
+
style?: React.CSSProperties;
|
|
56
|
+
}
|