@hydralms/components 0.1.3 → 0.2.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/ForumBoard-CHXU3mjC.js +2207 -0
- package/dist/ForumBoard-d1w5-r6n.cjs +1 -0
- package/dist/assessment-toolbar/assessment-toolbar.d.ts +1 -1
- package/dist/assessment-toolbar/index.d.ts +5 -1
- package/dist/assessment-toolbar/question-header-bar.d.ts +2 -0
- package/dist/assessment-toolbar/question-materials-drawer.d.ts +2 -0
- package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
- package/dist/assessment-toolbar/types.d.ts +52 -4
- package/dist/assessment-toolbar/use-countdown.d.ts +43 -0
- package/dist/common/index.d.ts +2 -1
- package/dist/common/stepper.d.ts +6 -0
- package/dist/common/types.d.ts +37 -0
- package/dist/components.css +1 -1
- package/dist/content/attachment-list.d.ts +6 -0
- package/dist/content/content-block.d.ts +1 -1
- package/dist/content/index.d.ts +2 -1
- package/dist/content/types.d.ts +39 -0
- package/dist/curriculum/curriculum-item.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +551 -312
- package/dist/modules/AssignmentModule/AssignmentModule.d.ts +8 -0
- package/dist/modules/AssignmentModule/types.d.ts +65 -0
- package/dist/modules/CertificateModule/CertificateModule.d.ts +9 -0
- package/dist/modules/CertificateModule/types.d.ts +49 -0
- package/dist/modules/DiscussionModule/DiscussionModule.d.ts +8 -0
- package/dist/modules/DiscussionModule/types.d.ts +47 -0
- package/dist/modules/ExamModule/ExamModule.d.ts +8 -0
- package/dist/modules/ExamModule/types.d.ts +64 -0
- package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +9 -0
- package/dist/modules/GradeCenterModule/types.d.ts +54 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +1 -1
- package/dist/modules/QuizModule/types.d.ts +6 -1
- package/dist/modules/SurveyModule/SurveyModule.d.ts +7 -0
- package/dist/modules/SurveyModule/types.d.ts +49 -0
- package/dist/modules/index.d.ts +12 -0
- package/dist/modules.cjs +1 -0
- package/dist/modules.js +1422 -0
- package/dist/progress/achievement-badge.d.ts +6 -0
- package/dist/progress/activity-timeline.d.ts +6 -0
- package/dist/progress/index.d.ts +4 -1
- package/dist/progress/stat-card.d.ts +1 -1
- package/dist/progress/streak-badge.d.ts +6 -0
- package/dist/progress/types.d.ts +97 -0
- package/dist/questions/essay.d.ts +1 -1
- package/dist/questions/hotspot.d.ts +21 -0
- package/dist/questions/index.d.ts +9 -1
- package/dist/questions/inline-choice.d.ts +21 -0
- package/dist/questions/matching.d.ts +22 -0
- package/dist/questions/numeric.d.ts +11 -0
- package/dist/questions/ordering.d.ts +12 -0
- package/dist/questions/scenario.d.ts +23 -0
- package/dist/questions/scoring.d.ts +22 -0
- package/dist/questions/spreadsheet.d.ts +29 -0
- package/dist/questions/types.d.ts +106 -1
- package/dist/questions/use-drag-reorder.d.ts +17 -0
- package/dist/sections/CertificateViewer/types.d.ts +7 -5
- package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
- package/dist/sections/ExamSession/types.d.ts +6 -1
- package/dist/sections/ForumBoard/ForumBoard.d.ts +8 -0
- package/dist/sections/ForumBoard/types.d.ts +64 -0
- package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
- package/dist/sections/QuizSession/types.d.ts +6 -1
- package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +8 -0
- package/dist/sections/RequirementsChecklist/types.d.ts +37 -0
- package/dist/sections/RubricView/RubricView.d.ts +9 -0
- package/dist/sections/RubricView/types.d.ts +50 -0
- package/dist/sections/index.d.ts +7 -1
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +250 -1715
- package/dist/social/post-card.d.ts +1 -1
- package/dist/tabs-DRM2Iq_J.cjs +172 -0
- package/dist/tabs-Wf3h_Cx3.js +21580 -0
- package/dist/ui/alert.d.ts +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/drawer.d.ts +84 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/rich-text-editor.d.ts +30 -0
- package/dist/ui/rich-text-toolbar.d.ts +8 -0
- package/dist/utils/array-utils.d.ts +4 -0
- package/dist/utils/flatten-leaves.d.ts +6 -0
- package/dist/utils/format-file-size.d.ts +1 -0
- package/dist/utils/format-timestamp.d.ts +1 -0
- package/dist/utils/is-empty-html.d.ts +5 -0
- package/dist/utils/shuffle.d.ts +1 -0
- package/dist/utils/string-utils.d.ts +12 -0
- package/dist/video/video-bookmark.d.ts +1 -1
- package/dist/video/video-playlist-item.d.ts +1 -1
- package/package.json +92 -3
- package/src/assessment-toolbar/assessment-toolbar.tsx +54 -49
- package/src/assessment-toolbar/index.ts +6 -0
- package/src/assessment-toolbar/question-header-bar.tsx +61 -0
- package/src/assessment-toolbar/question-materials-drawer.tsx +55 -0
- package/src/assessment-toolbar/question-navigator.tsx +3 -31
- package/src/assessment-toolbar/timer-display.tsx +2 -2
- package/src/assessment-toolbar/types.ts +54 -4
- package/src/assessment-toolbar/use-countdown.ts +153 -0
- package/src/common/index.ts +3 -0
- package/src/common/search-input.tsx +7 -6
- package/src/common/stepper.tsx +100 -0
- package/src/common/types.ts +39 -0
- package/src/content/attachment-list.tsx +90 -0
- package/src/content/content-block.tsx +4 -2
- package/src/content/file-upload-zone.tsx +1 -6
- package/src/content/index.ts +3 -0
- package/src/content/types.ts +41 -0
- package/src/curriculum/curriculum-item.tsx +7 -3
- package/src/feedback/feedback-banner.tsx +12 -14
- package/src/flashcards/flashcard-deck.tsx +1 -9
- package/src/flashcards/flashcard.tsx +1 -1
- package/src/modules/AssignmentModule/AssignmentModule.tsx +305 -0
- package/src/modules/AssignmentModule/types.ts +73 -0
- package/src/modules/CertificateModule/CertificateModule.tsx +161 -0
- package/src/modules/CertificateModule/types.ts +47 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +44 -48
- package/src/modules/DiscussionModule/DiscussionModule.tsx +110 -0
- package/src/modules/DiscussionModule/types.ts +54 -0
- package/src/modules/ExamModule/ExamModule.tsx +285 -0
- package/src/modules/ExamModule/types.ts +66 -0
- package/src/modules/FlashcardLab/FlashcardLab.tsx +29 -16
- package/src/modules/GradeCenterModule/GradeCenterModule.tsx +169 -0
- package/src/modules/GradeCenterModule/types.ts +63 -0
- package/src/modules/QuizModule/QuizModule.tsx +88 -88
- package/src/modules/QuizModule/types.ts +6 -1
- package/src/modules/SurveyModule/SurveyModule.tsx +180 -0
- package/src/modules/SurveyModule/types.ts +51 -0
- package/src/modules/index.ts +24 -0
- package/src/progress/achievement-badge.tsx +52 -0
- package/src/progress/activity-timeline.tsx +84 -0
- package/src/progress/index.ts +7 -0
- package/src/progress/stat-card.tsx +30 -18
- package/src/progress/streak-badge.tsx +35 -0
- package/src/progress/types.ts +101 -0
- package/src/questions/choice.tsx +7 -9
- package/src/questions/essay.tsx +23 -25
- package/src/questions/fill-in-the-blank.tsx +13 -16
- package/src/questions/hotspot.tsx +154 -0
- package/src/questions/index.ts +16 -0
- package/src/questions/inline-choice.tsx +151 -0
- package/src/questions/matching.tsx +228 -0
- package/src/questions/multiple-choice.tsx +7 -9
- package/src/questions/numeric.tsx +102 -0
- package/src/questions/ordering.tsx +159 -0
- package/src/questions/question-renderer.tsx +21 -0
- package/src/questions/scenario.tsx +140 -0
- package/src/questions/scoring.ts +201 -0
- package/src/questions/spreadsheet.tsx +259 -0
- package/src/questions/true-false.tsx +7 -9
- package/src/questions/types.ts +123 -1
- package/src/questions/use-drag-reorder.ts +80 -0
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +2 -15
- package/src/sections/AssessmentReview/AssessmentReview.tsx +13 -2
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +7 -5
- package/src/sections/CertificateViewer/CertificateViewer.tsx +409 -56
- package/src/sections/CertificateViewer/types.ts +13 -5
- package/src/sections/CourseOutline/CourseOutline.tsx +4 -14
- package/src/sections/DiscussionThread/DiscussionThread.tsx +13 -10
- package/src/sections/ExamSession/ExamSession.tsx +44 -7
- package/src/sections/ExamSession/types.ts +6 -1
- package/src/sections/ForumBoard/ForumBoard.tsx +284 -0
- package/src/sections/ForumBoard/types.ts +67 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +1 -1
- package/src/sections/LecturePlayer/LecturePlayer.tsx +1 -1
- package/src/sections/LessonPage/LessonPage.tsx +5 -9
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +15 -26
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +65 -65
- package/src/sections/QuizSession/QuizSession.tsx +67 -8
- package/src/sections/QuizSession/types.ts +6 -1
- package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +107 -0
- package/src/sections/RequirementsChecklist/types.ts +38 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +4 -9
- package/src/sections/RubricView/RubricView.tsx +138 -0
- package/src/sections/RubricView/types.ts +52 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +23 -9
- package/src/sections/SurveyForm/SurveyForm.tsx +8 -5
- package/src/sections/index.ts +20 -1
- package/src/social/post-card.tsx +8 -19
- package/src/social/user-avatar.tsx +1 -0
- package/src/styles/globals.css +13 -0
- package/src/ui/drawer.tsx +600 -0
- package/src/ui/index.ts +19 -0
- package/src/ui/rich-text-editor.tsx +109 -0
- package/src/ui/rich-text-toolbar.tsx +156 -0
- package/src/utils/array-utils.ts +17 -0
- package/src/utils/flatten-leaves.ts +17 -0
- package/src/utils/format-file-size.ts +5 -0
- package/src/utils/format-timestamp.ts +13 -0
- package/src/utils/is-empty-html.ts +7 -0
- package/src/utils/shuffle.ts +8 -0
- package/src/utils/string-utils.ts +30 -0
- package/src/video/video-bookmark.tsx +4 -3
- package/src/video/video-chapter-list.tsx +9 -4
- package/src/video/video-player.tsx +11 -4
- package/src/video/video-playlist-item.tsx +8 -3
- package/src/video/video-thumbnail-card.tsx +4 -0
- package/src/video/video-transcript.tsx +8 -5
- package/dist/table-BrS5cDQu.js +0 -2510
- package/dist/table-D6AkBBEo.cjs +0 -1
package/src/questions/types.ts
CHANGED
|
@@ -3,7 +3,17 @@ export type QuestionTypeEnum =
|
|
|
3
3
|
| "choice"
|
|
4
4
|
| "true_false"
|
|
5
5
|
| "fill_in_the_blank"
|
|
6
|
-
| "essay"
|
|
6
|
+
| "essay"
|
|
7
|
+
| "matching"
|
|
8
|
+
| "ordering"
|
|
9
|
+
| "numeric"
|
|
10
|
+
| "hotspot"
|
|
11
|
+
| "inline_choice"
|
|
12
|
+
| "scenario"
|
|
13
|
+
| "spreadsheet";
|
|
14
|
+
|
|
15
|
+
/** Scoring strategy for scenario questions. */
|
|
16
|
+
export type ScenarioScoringMode = "per_question" | "all_or_nothing";
|
|
7
17
|
|
|
8
18
|
export interface AnswerOption {
|
|
9
19
|
uid: string;
|
|
@@ -20,12 +30,124 @@ export interface SessionAnswer {
|
|
|
20
30
|
confidence?: string;
|
|
21
31
|
}
|
|
22
32
|
|
|
33
|
+
/** A pair linking a left-side item to its correct right-side target. */
|
|
34
|
+
export interface MatchingPair {
|
|
35
|
+
uid: string;
|
|
36
|
+
/** Left-side item content (HTML) */
|
|
37
|
+
item: string;
|
|
38
|
+
/** Right-side target content (HTML) */
|
|
39
|
+
target: string;
|
|
40
|
+
sequence: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A clickable region on a hotspot image. Coordinates are percentages (0–100). */
|
|
44
|
+
export interface HotspotRegion {
|
|
45
|
+
uid: string;
|
|
46
|
+
shape: "rect" | "circle";
|
|
47
|
+
/** rect: [x, y, width, height]; circle: [centerX, centerY, radius] */
|
|
48
|
+
coords: number[];
|
|
49
|
+
isCorrect?: boolean;
|
|
50
|
+
label?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A dropdown blank embedded in an inline-choice (cloze) passage. */
|
|
54
|
+
export interface InlineBlank {
|
|
55
|
+
uid: string;
|
|
56
|
+
options: { uid: string; content: string; isCorrect?: boolean }[];
|
|
57
|
+
sequence: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A column definition for a spreadsheet question. */
|
|
61
|
+
export interface SpreadsheetColumn {
|
|
62
|
+
uid: string;
|
|
63
|
+
/** Column header text */
|
|
64
|
+
label: string;
|
|
65
|
+
/** Data type hint for input rendering and scoring */
|
|
66
|
+
type: "text" | "numeric" | "currency";
|
|
67
|
+
/** Column width in rems (default 8) */
|
|
68
|
+
width?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A single cell in a spreadsheet row. */
|
|
72
|
+
export interface SpreadsheetCell {
|
|
73
|
+
/** Unique identifier — becomes the answerUid in SessionAnswer */
|
|
74
|
+
uid: string;
|
|
75
|
+
/** Which column this cell belongs to */
|
|
76
|
+
columnUid: string;
|
|
77
|
+
/** When true, cell is pre-filled and not editable */
|
|
78
|
+
locked: boolean;
|
|
79
|
+
/** Display value for locked cells, or pre-fill for editable cells */
|
|
80
|
+
value?: string;
|
|
81
|
+
/** Expected answer for grading (editable cells only) */
|
|
82
|
+
correctAnswer?: string;
|
|
83
|
+
/** Acceptable numeric deviation (default 0 = exact match) */
|
|
84
|
+
tolerance?: number;
|
|
85
|
+
/** Hint shown in review mode (e.g., "= Revenue – Expenses") */
|
|
86
|
+
formula?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A row in a spreadsheet question grid. */
|
|
90
|
+
export interface SpreadsheetRow {
|
|
91
|
+
uid: string;
|
|
92
|
+
/** Optional row header label (e.g., "Cash", "Accounts Receivable") */
|
|
93
|
+
label?: string;
|
|
94
|
+
/** Ordered cells — one per column */
|
|
95
|
+
cells: SpreadsheetCell[];
|
|
96
|
+
/** Renders as a bold section divider row */
|
|
97
|
+
isHeader?: boolean;
|
|
98
|
+
/** Renders in table footer with top border */
|
|
99
|
+
isTotals?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Content blocks associated with a question for open-book reference. */
|
|
103
|
+
export interface QuestionMaterial {
|
|
104
|
+
/** UID of the question this material is linked to */
|
|
105
|
+
questionUid: string;
|
|
106
|
+
/** Display label for the material group (e.g., "Chapter 3: Bonds") */
|
|
107
|
+
label?: string;
|
|
108
|
+
/** Content blocks to render in the materials drawer */
|
|
109
|
+
blocks: import("../content/types").LessonBlock[];
|
|
110
|
+
}
|
|
111
|
+
|
|
23
112
|
export interface QuestionData {
|
|
24
113
|
uid: string;
|
|
25
114
|
type: QuestionTypeEnum;
|
|
115
|
+
/** Question prompt (HTML) */
|
|
26
116
|
content: string;
|
|
117
|
+
/** Explanation shown in review mode (HTML) */
|
|
27
118
|
explanation?: string;
|
|
119
|
+
/** Answer options for choice / multiple_choice / true_false / ordering */
|
|
28
120
|
answers?: AnswerOption[];
|
|
121
|
+
|
|
122
|
+
/** Matching-specific: pairs of items and targets */
|
|
123
|
+
matchingPairs?: MatchingPair[];
|
|
124
|
+
|
|
125
|
+
/** Numeric-specific: correct answer */
|
|
126
|
+
numericAnswer?: number;
|
|
127
|
+
/** Numeric-specific: acceptable deviation (default 0 = exact) */
|
|
128
|
+
numericTolerance?: number;
|
|
129
|
+
/** Numeric-specific: unit label displayed after input */
|
|
130
|
+
numericUnit?: string;
|
|
131
|
+
|
|
132
|
+
/** Hotspot-specific: image URL */
|
|
133
|
+
hotspotImageUrl?: string;
|
|
134
|
+
/** Hotspot-specific: clickable regions */
|
|
135
|
+
hotspotRegions?: HotspotRegion[];
|
|
136
|
+
/** Hotspot-specific: allow selecting multiple regions */
|
|
137
|
+
hotspotMultiSelect?: boolean;
|
|
138
|
+
|
|
139
|
+
/** InlineChoice-specific: dropdown blanks embedded in content */
|
|
140
|
+
inlineBlanks?: InlineBlank[];
|
|
141
|
+
|
|
142
|
+
/** Scenario-specific: sub-questions grouped under this scenario's shared context */
|
|
143
|
+
scenarioQuestions?: QuestionData[];
|
|
144
|
+
/** Scenario-specific: how the scenario is scored (default "per_question") */
|
|
145
|
+
scenarioScoringMode?: ScenarioScoringMode;
|
|
146
|
+
|
|
147
|
+
/** Spreadsheet-specific: column definitions */
|
|
148
|
+
spreadsheetColumns?: SpreadsheetColumn[];
|
|
149
|
+
/** Spreadsheet-specific: row data with cells */
|
|
150
|
+
spreadsheetRows?: SpreadsheetRow[];
|
|
29
151
|
}
|
|
30
152
|
|
|
31
153
|
/**
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
interface UseDragReorderOptions<T> {
|
|
4
|
+
items: T[];
|
|
5
|
+
onReorder: (items: T[]) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useDragReorder<T>({
|
|
10
|
+
items,
|
|
11
|
+
onReorder,
|
|
12
|
+
disabled = false,
|
|
13
|
+
}: UseDragReorderOptions<T>) {
|
|
14
|
+
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
|
15
|
+
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
|
16
|
+
const dragIndexRef = useRef<number | null>(null);
|
|
17
|
+
|
|
18
|
+
const handleDragStart = useCallback(
|
|
19
|
+
(index: number) => (e: React.DragEvent) => {
|
|
20
|
+
if (disabled) {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
dragIndexRef.current = index;
|
|
25
|
+
setDragIndex(index);
|
|
26
|
+
e.dataTransfer.effectAllowed = "move";
|
|
27
|
+
e.dataTransfer.setData("text/plain", String(index));
|
|
28
|
+
},
|
|
29
|
+
[disabled],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const handleDragOver = useCallback(
|
|
33
|
+
(index: number) => (e: React.DragEvent) => {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
e.dataTransfer.dropEffect = "move";
|
|
36
|
+
setDragOverIndex(index);
|
|
37
|
+
},
|
|
38
|
+
[],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const handleDrop = useCallback(
|
|
42
|
+
(index: number) => (e: React.DragEvent) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
const fromIndex = dragIndexRef.current;
|
|
45
|
+
if (fromIndex === null || fromIndex === index) {
|
|
46
|
+
setDragIndex(null);
|
|
47
|
+
setDragOverIndex(null);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const next = [...items];
|
|
52
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
53
|
+
next.splice(index, 0, moved);
|
|
54
|
+
|
|
55
|
+
setDragIndex(null);
|
|
56
|
+
setDragOverIndex(null);
|
|
57
|
+
onReorder(next);
|
|
58
|
+
},
|
|
59
|
+
[items, onReorder],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const handleDragEnd = useCallback(() => {
|
|
63
|
+
setDragIndex(null);
|
|
64
|
+
setDragOverIndex(null);
|
|
65
|
+
dragIndexRef.current = null;
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const getDragProps = useCallback(
|
|
69
|
+
(index: number) => ({
|
|
70
|
+
draggable: !disabled,
|
|
71
|
+
onDragStart: handleDragStart(index),
|
|
72
|
+
onDragOver: handleDragOver(index),
|
|
73
|
+
onDrop: handleDrop(index),
|
|
74
|
+
onDragEnd: handleDragEnd,
|
|
75
|
+
}),
|
|
76
|
+
[disabled, handleDragStart, handleDragOver, handleDrop, handleDragEnd],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return { getDragProps, dragIndex, dragOverIndex };
|
|
80
|
+
}
|
|
@@ -7,20 +7,7 @@ import { Button } from "../../ui/button";
|
|
|
7
7
|
import { Card, CardContent } from "../../ui/card";
|
|
8
8
|
import type { AnnouncementFeedProps } from "./types";
|
|
9
9
|
import { cn } from "../../lib/utils";
|
|
10
|
-
|
|
11
|
-
function formatTimestamp(iso: string): string {
|
|
12
|
-
const date = new Date(iso);
|
|
13
|
-
const now = new Date();
|
|
14
|
-
const diffMs = now.getTime() - date.getTime();
|
|
15
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
16
|
-
if (diffMins < 1) return "Just now";
|
|
17
|
-
if (diffMins < 60) return `${diffMins}m ago`;
|
|
18
|
-
const diffHours = Math.floor(diffMins / 60);
|
|
19
|
-
if (diffHours < 24) return `${diffHours}h ago`;
|
|
20
|
-
const diffDays = Math.floor(diffHours / 24);
|
|
21
|
-
if (diffDays < 7) return `${diffDays}d ago`;
|
|
22
|
-
return date.toLocaleDateString();
|
|
23
|
-
}
|
|
10
|
+
import { formatTimestamp } from "../../utils/format-timestamp";
|
|
24
11
|
|
|
25
12
|
export function AnnouncementFeed({
|
|
26
13
|
announcements,
|
|
@@ -77,7 +64,7 @@ export function AnnouncementFeed({
|
|
|
77
64
|
)}
|
|
78
65
|
onClick={() => onSelect && !readOnly ? onSelect(a) : toggleExpand(a.uid)}
|
|
79
66
|
>
|
|
80
|
-
<CardContent className="
|
|
67
|
+
<CardContent className="py-4">
|
|
81
68
|
<div className="flex gap-1.5 items-start">
|
|
82
69
|
{showAvatars && (
|
|
83
70
|
<UserAvatar
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import { QuestionRenderer } from "../../questions";
|
|
2
3
|
import type { QuestionData, SessionAnswer } from "../../questions/types";
|
|
3
4
|
import { Badge } from "../../ui/badge";
|
|
@@ -25,7 +26,7 @@ function ScoreHeader({
|
|
|
25
26
|
<Card className="mb-3">
|
|
26
27
|
<CardContent className="pt-6">
|
|
27
28
|
<div className="flex flex-wrap items-center gap-2">
|
|
28
|
-
<div>
|
|
29
|
+
<div className="flex items-baseline gap-2">
|
|
29
30
|
<span className="text-2xl font-bold leading-none text-foreground">{pct}%</span>
|
|
30
31
|
<span className="text-sm text-muted-foreground">
|
|
31
32
|
{score.correct} of {score.total} correct
|
|
@@ -56,6 +57,16 @@ function QuestionList({
|
|
|
56
57
|
sessionAnswers: SessionAnswer[];
|
|
57
58
|
showCorrectAnswers: boolean;
|
|
58
59
|
}) {
|
|
60
|
+
const answersByQuestion = useMemo(() => {
|
|
61
|
+
const map = new Map<string, SessionAnswer[]>();
|
|
62
|
+
for (const a of sessionAnswers) {
|
|
63
|
+
const list = map.get(a.uid);
|
|
64
|
+
if (list) list.push(a);
|
|
65
|
+
else map.set(a.uid, [a]);
|
|
66
|
+
}
|
|
67
|
+
return map;
|
|
68
|
+
}, [sessionAnswers]);
|
|
69
|
+
|
|
59
70
|
return (
|
|
60
71
|
<div className="flex flex-col gap-3">
|
|
61
72
|
{questions.map((question, idx) => (
|
|
@@ -69,7 +80,7 @@ function QuestionList({
|
|
|
69
80
|
<CardContent className="pt-4 pb-4">
|
|
70
81
|
<QuestionRenderer
|
|
71
82
|
question={question}
|
|
72
|
-
sessionAnswers={
|
|
83
|
+
sessionAnswers={answersByQuestion.get(question.uid) ?? []}
|
|
73
84
|
readOnly
|
|
74
85
|
showCorrectAnswers={showCorrectAnswers}
|
|
75
86
|
/>
|
|
@@ -3,7 +3,7 @@ import { Send, Save } from "lucide-react";
|
|
|
3
3
|
import { StatusBadge, DueDateDisplay } from "../../common";
|
|
4
4
|
import { FileUploadZone } from "../../content";
|
|
5
5
|
import { Button } from "../../ui/button";
|
|
6
|
-
import {
|
|
6
|
+
import { RichTextEditor } from "../../ui/rich-text-editor";
|
|
7
7
|
import { Input } from "../../ui/input";
|
|
8
8
|
import { Separator } from "../../ui/separator";
|
|
9
9
|
import { Card, CardContent } from "../../ui/card";
|
|
@@ -99,11 +99,12 @@ export function AssignmentSubmission({
|
|
|
99
99
|
</TabsList>
|
|
100
100
|
{submissionTypes.includes("text") && (
|
|
101
101
|
<TabsContent value="text">
|
|
102
|
-
<
|
|
102
|
+
<RichTextEditor
|
|
103
103
|
className="min-h-45"
|
|
104
104
|
placeholder="Type your submission..."
|
|
105
105
|
value={textContent}
|
|
106
|
-
onChange={(
|
|
106
|
+
onChange={(html) => setTextContent(html)}
|
|
107
|
+
variant="default"
|
|
107
108
|
/>
|
|
108
109
|
</TabsContent>
|
|
109
110
|
)}
|
|
@@ -132,11 +133,12 @@ export function AssignmentSubmission({
|
|
|
132
133
|
) : (
|
|
133
134
|
<>
|
|
134
135
|
{submissionTypes.includes("text") && (
|
|
135
|
-
<
|
|
136
|
+
<RichTextEditor
|
|
136
137
|
className="min-h-45 mb-2"
|
|
137
138
|
placeholder="Type your submission..."
|
|
138
139
|
value={textContent}
|
|
139
|
-
onChange={(
|
|
140
|
+
onChange={(html) => setTextContent(html)}
|
|
141
|
+
variant="default"
|
|
140
142
|
/>
|
|
141
143
|
)}
|
|
142
144
|
{submissionTypes.includes("file") && (
|