@burdenoff/microfe-buildmyiq 2026.705.13 → 2026.706.10
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/buildmyiq/BuildmyiqRoot.d.ts.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoot.js +57 -49
- package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoutes.d.ts.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoutes.js +15 -11
- package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
- package/dist/buildmyiq/components/Avatar.d.ts +15 -0
- package/dist/buildmyiq/components/Avatar.d.ts.map +1 -0
- package/dist/buildmyiq/components/Avatar.js +42 -0
- package/dist/buildmyiq/components/Avatar.js.map +1 -0
- package/dist/buildmyiq/components/Viz.d.ts +25 -0
- package/dist/buildmyiq/components/Viz.d.ts.map +1 -0
- package/dist/buildmyiq/components/Viz.js +78 -0
- package/dist/buildmyiq/components/Viz.js.map +1 -0
- package/dist/buildmyiq/components/index.d.ts +2 -0
- package/dist/buildmyiq/components/index.d.ts.map +1 -1
- package/dist/buildmyiq/components/index.js +2 -0
- package/dist/buildmyiq/pages/DashboardPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/DashboardPage.js +200 -98
- package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js +262 -241
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.js +142 -115
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +220 -156
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js +51 -46
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +581 -448
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/catalog/CatalogPage.js +52 -42
- package/dist/buildmyiq/pages/catalog/CatalogPage.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +422 -301
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js +237 -151
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js +219 -138
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
- package/dist/buildmyiq/pages/insights/InsightsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/insights/InsightsPage.js +276 -152
- package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +237 -174
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
- package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/learning/MyLearningPage.js +307 -169
- package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
- package/dist/buildmyiq/pages/paths/PathsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/paths/PathsPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/paths/PathsPage.js +187 -0
- package/dist/buildmyiq/pages/paths/PathsPage.js.map +1 -0
- package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/skills/SkillsPage.js +233 -140
- package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +379 -275
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { useBuildmyiq as e } from "../../context/BuildmyiqContext.js";
|
|
2
2
|
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { MetricsCard as n } from "../../components/MetricsCard.js";
|
|
4
|
+
import { formatEnumLabel as ee, formatRelativeTime as te } from "../../utils/formatters.js";
|
|
5
|
+
import { StatusBadge as r } from "../../components/StatusBadge.js";
|
|
6
|
+
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
7
|
+
import { ErrorView as ne, LoadingView as a } from "../../components/StateViews.js";
|
|
8
|
+
import { DataTable as o } from "../../components/DataTable.js";
|
|
9
|
+
import { FilterTabs as s } from "../../components/FilterTabs.js";
|
|
9
10
|
import "../../context/index.js";
|
|
10
|
-
import { useBuiAssessments as
|
|
11
|
+
import { useBuiAssessments as c, useBuiCourses as l, useBuiCreateAssessment as u } from "../../hooks/useBuildmyiqApi.js";
|
|
11
12
|
import "../../hooks/index.js";
|
|
12
13
|
import "../../components/index.js";
|
|
13
|
-
import { ASSESSMENT_TYPES as
|
|
14
|
-
import { useMemo as
|
|
15
|
-
import { ClipboardList as h, Plus as g } from "lucide-react";
|
|
14
|
+
import { ASSESSMENT_TYPES as re } from "../../types/index.js";
|
|
15
|
+
import { useMemo as d, useState as f } from "react";
|
|
16
|
+
import { CheckCircle2 as ie, ClipboardList as p, FileEdit as m, ListChecks as h, Plus as g } from "lucide-react";
|
|
16
17
|
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
17
|
-
import { Button as y, GlassCard as
|
|
18
|
+
import { Button as y, GlassCard as b, Input as x, Label as S, ResponsiveDialog as C, ResponsiveDialogContent as w, ResponsiveDialogDescription as ae, ResponsiveDialogFooter as oe, ResponsiveDialogHeader as se, ResponsiveDialogTitle as ce, Select as T, SelectContent as E, SelectItem as D, SelectTrigger as O, SelectValue as k, Switch as A, Textarea as j } from "@burdenoff/fe-libs/ui";
|
|
18
19
|
//#region src/buildmyiq/pages/assessments/AssessmentsPage.tsx
|
|
19
|
-
var
|
|
20
|
+
var M = [
|
|
20
21
|
{
|
|
21
22
|
value: "ALL",
|
|
22
23
|
label: "All"
|
|
@@ -33,7 +34,7 @@ var re = [
|
|
|
33
34
|
value: "ARCHIVED",
|
|
34
35
|
label: "Archived"
|
|
35
36
|
}
|
|
36
|
-
],
|
|
37
|
+
], N = () => ({
|
|
37
38
|
title: "",
|
|
38
39
|
description: "",
|
|
39
40
|
type: "QUIZ",
|
|
@@ -41,21 +42,21 @@ var re = [
|
|
|
41
42
|
maxAttempts: "1",
|
|
42
43
|
timeLimitMin: "0",
|
|
43
44
|
isGraded: !0
|
|
44
|
-
}),
|
|
45
|
-
let { navigateTo:
|
|
45
|
+
}), P = () => {
|
|
46
|
+
let { navigateTo: P } = e(), [F, I] = f(""), [L, R] = f("ALL"), [z, B] = f(!1), [V, H] = f(N), [U, W] = f(null), G = l({ limit: 100 }), K = c(F || void 0), q = u(), J = G.data?.items ?? [], Y = d(() => K.data ?? [], [K.data]), X = d(() => {
|
|
46
47
|
let e = {
|
|
47
|
-
ALL:
|
|
48
|
+
ALL: Y.length,
|
|
48
49
|
DRAFT: 0,
|
|
49
50
|
PUBLISHED: 0,
|
|
50
51
|
ARCHIVED: 0
|
|
51
52
|
};
|
|
52
|
-
for (let t of
|
|
53
|
+
for (let t of Y) e[t.status] += 1;
|
|
53
54
|
return e;
|
|
54
|
-
}, [
|
|
55
|
+
}, [Y]), Z = d(() => L === "ALL" ? Y : Y.filter((e) => e.status === L), [Y, L]), le = d(() => Y.reduce((e, t) => e + (t.questions?.length ?? 0), 0), [Y]), ue = M.map((e) => ({
|
|
55
56
|
value: e.value,
|
|
56
57
|
label: e.label,
|
|
57
|
-
count:
|
|
58
|
-
})),
|
|
58
|
+
count: X[e.value]
|
|
59
|
+
})), de = [
|
|
59
60
|
{
|
|
60
61
|
key: "title",
|
|
61
62
|
header: "Assessment",
|
|
@@ -74,13 +75,13 @@ var re = [
|
|
|
74
75
|
{
|
|
75
76
|
key: "type",
|
|
76
77
|
header: "Type",
|
|
77
|
-
render: (e) => /* @__PURE__ */ _(
|
|
78
|
+
render: (e) => /* @__PURE__ */ _(r, { status: e.type })
|
|
78
79
|
},
|
|
79
80
|
{
|
|
80
81
|
key: "status",
|
|
81
82
|
header: "Status",
|
|
82
83
|
sortValue: (e) => e.status,
|
|
83
|
-
render: (e) => /* @__PURE__ */ _(
|
|
84
|
+
render: (e) => /* @__PURE__ */ _(r, { status: e.status })
|
|
84
85
|
},
|
|
85
86
|
{
|
|
86
87
|
key: "passingScore",
|
|
@@ -110,13 +111,13 @@ var re = [
|
|
|
110
111
|
sortValue: (e) => e.updatedAt,
|
|
111
112
|
render: (e) => /* @__PURE__ */ _("span", {
|
|
112
113
|
className: "whitespace-nowrap text-text-secondary",
|
|
113
|
-
children:
|
|
114
|
+
children: te(e.updatedAt)
|
|
114
115
|
})
|
|
115
116
|
}
|
|
116
117
|
], Q = () => {
|
|
117
|
-
|
|
118
|
+
H(N()), W(null), B(!0);
|
|
118
119
|
}, $ = () => {
|
|
119
|
-
|
|
120
|
+
B(!1), H(N()), W(null);
|
|
120
121
|
};
|
|
121
122
|
return /* @__PURE__ */ v(t, {
|
|
122
123
|
title: "Assessments",
|
|
@@ -125,11 +126,11 @@ var re = [
|
|
|
125
126
|
aurora: !0,
|
|
126
127
|
actions: /* @__PURE__ */ v(y, {
|
|
127
128
|
onClick: Q,
|
|
128
|
-
disabled: !
|
|
129
|
+
disabled: !F,
|
|
129
130
|
children: [/* @__PURE__ */ _(g, { className: "mr-1.5 h-4 w-4" }), " New assessment"]
|
|
130
131
|
}),
|
|
131
132
|
children: [
|
|
132
|
-
/* @__PURE__ */ _(
|
|
133
|
+
/* @__PURE__ */ _(b, {
|
|
133
134
|
treatment: "plain",
|
|
134
135
|
className: "mb-5 p-4",
|
|
135
136
|
children: /* @__PURE__ */ v("div", {
|
|
@@ -141,84 +142,110 @@ var re = [
|
|
|
141
142
|
className: "text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
142
143
|
children: "Course"
|
|
143
144
|
}),
|
|
144
|
-
/* @__PURE__ */ v(
|
|
145
|
-
value:
|
|
145
|
+
/* @__PURE__ */ v(T, {
|
|
146
|
+
value: F,
|
|
146
147
|
onValueChange: (e) => {
|
|
147
|
-
|
|
148
|
+
I(e), R("ALL");
|
|
148
149
|
},
|
|
149
|
-
children: [/* @__PURE__ */ _(
|
|
150
|
+
children: [/* @__PURE__ */ _(O, {
|
|
150
151
|
className: "w-full sm:w-72",
|
|
151
|
-
children: /* @__PURE__ */ _(
|
|
152
|
-
}), /* @__PURE__ */ _(
|
|
152
|
+
children: /* @__PURE__ */ _(k, { placeholder: "Select a course" })
|
|
153
|
+
}), /* @__PURE__ */ _(E, { children: J.map((e) => /* @__PURE__ */ _(D, {
|
|
153
154
|
value: e.id,
|
|
154
155
|
children: e.title
|
|
155
156
|
}, e.id)) })]
|
|
156
157
|
}),
|
|
157
|
-
|
|
158
|
+
G.isSuccess && J.length === 0 && /* @__PURE__ */ _("p", {
|
|
158
159
|
className: "text-xs text-text-secondary",
|
|
159
160
|
children: "No courses yet — create one in Authoring first."
|
|
160
161
|
})
|
|
161
162
|
]
|
|
162
|
-
}),
|
|
163
|
+
}), F && /* @__PURE__ */ v("div", {
|
|
163
164
|
className: "grid gap-1.5",
|
|
164
165
|
children: [/* @__PURE__ */ _("span", {
|
|
165
166
|
className: "text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
166
167
|
children: "Status"
|
|
167
|
-
}), /* @__PURE__ */ _(
|
|
168
|
-
options:
|
|
169
|
-
value:
|
|
170
|
-
onChange:
|
|
168
|
+
}), /* @__PURE__ */ _(s, {
|
|
169
|
+
options: ue,
|
|
170
|
+
value: L,
|
|
171
|
+
onChange: R
|
|
171
172
|
})]
|
|
172
173
|
})]
|
|
173
174
|
})
|
|
174
175
|
}),
|
|
175
|
-
|
|
176
|
+
F && K.isSuccess && Y.length > 0 && /* @__PURE__ */ v("div", {
|
|
177
|
+
className: "mb-5 grid grid-cols-2 gap-4 lg:grid-cols-4",
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ _(n, {
|
|
180
|
+
icon: /* @__PURE__ */ _(p, { className: "h-5 w-5" }),
|
|
181
|
+
value: X.ALL,
|
|
182
|
+
label: "Assessments"
|
|
183
|
+
}),
|
|
184
|
+
/* @__PURE__ */ _(n, {
|
|
185
|
+
icon: /* @__PURE__ */ _(ie, { className: "h-5 w-5" }),
|
|
186
|
+
value: X.PUBLISHED,
|
|
187
|
+
label: "Published",
|
|
188
|
+
accent: "success"
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ _(n, {
|
|
191
|
+
icon: /* @__PURE__ */ _(m, { className: "h-5 w-5" }),
|
|
192
|
+
value: X.DRAFT,
|
|
193
|
+
label: "Drafts"
|
|
194
|
+
}),
|
|
195
|
+
/* @__PURE__ */ _(n, {
|
|
196
|
+
icon: /* @__PURE__ */ _(h, { className: "h-5 w-5" }),
|
|
197
|
+
value: le,
|
|
198
|
+
label: "Questions"
|
|
199
|
+
})
|
|
200
|
+
]
|
|
201
|
+
}),
|
|
202
|
+
F ? K.isLoading ? /* @__PURE__ */ _(a, { label: "Loading assessments…" }) : K.isError ? /* @__PURE__ */ _(ne, {
|
|
176
203
|
message: "Couldn't load assessments for this course.",
|
|
177
|
-
onRetry: () =>
|
|
178
|
-
}) : Z.length === 0 ? /* @__PURE__ */ _(
|
|
204
|
+
onRetry: () => K.refetch()
|
|
205
|
+
}) : Z.length === 0 ? /* @__PURE__ */ _(i, {
|
|
179
206
|
illustration: "empty-data",
|
|
180
|
-
icon: /* @__PURE__ */ _(
|
|
181
|
-
title:
|
|
182
|
-
description:
|
|
207
|
+
icon: /* @__PURE__ */ _(p, { className: "h-10 w-10" }),
|
|
208
|
+
title: L === "ALL" ? "No assessments yet" : "No assessments match this filter",
|
|
209
|
+
description: L === "ALL" ? "Create the first quiz, exam or assignment for this course." : "Try a different status filter, or create a new assessment.",
|
|
183
210
|
action: /* @__PURE__ */ v(y, {
|
|
184
211
|
onClick: Q,
|
|
185
212
|
children: [/* @__PURE__ */ _(g, { className: "mr-1.5 h-4 w-4" }), " New assessment"]
|
|
186
213
|
})
|
|
187
|
-
}) : /* @__PURE__ */ _(
|
|
188
|
-
columns:
|
|
214
|
+
}) : /* @__PURE__ */ _(o, {
|
|
215
|
+
columns: de,
|
|
189
216
|
rows: Z,
|
|
190
217
|
rowKey: (e) => e.id,
|
|
191
|
-
onRowClick: (e) =>
|
|
218
|
+
onRowClick: (e) => P(`assessments/${e.id}`),
|
|
192
219
|
defaultSort: {
|
|
193
220
|
key: "updatedAt",
|
|
194
221
|
dir: "desc"
|
|
195
222
|
}
|
|
196
|
-
}) : /* @__PURE__ */ _(
|
|
223
|
+
}) : /* @__PURE__ */ _(i, {
|
|
197
224
|
illustration: "onboarding",
|
|
198
|
-
icon: /* @__PURE__ */ _(
|
|
225
|
+
icon: /* @__PURE__ */ _(p, { className: "h-10 w-10" }),
|
|
199
226
|
title: "Pick a course to see its assessments",
|
|
200
227
|
description: "Assessments are scoped to a course. Choose a course above to view, filter and create its quizzes and exams."
|
|
201
228
|
}),
|
|
202
|
-
/* @__PURE__ */ _(
|
|
203
|
-
open:
|
|
229
|
+
/* @__PURE__ */ _(C, {
|
|
230
|
+
open: z,
|
|
204
231
|
onOpenChange: (e) => {
|
|
205
|
-
e ?
|
|
232
|
+
e ? B(!0) : $();
|
|
206
233
|
},
|
|
207
|
-
children: /* @__PURE__ */ v(
|
|
208
|
-
/* @__PURE__ */ v(
|
|
234
|
+
children: /* @__PURE__ */ v(w, { children: [
|
|
235
|
+
/* @__PURE__ */ v(se, { children: [/* @__PURE__ */ _(ce, { children: "New assessment" }), /* @__PURE__ */ _(ae, { children: "Add a quiz, exam or assignment to this course. You can add questions after it's created." })] }),
|
|
209
236
|
/* @__PURE__ */ v("div", {
|
|
210
237
|
className: "grid gap-4 py-2",
|
|
211
238
|
children: [
|
|
212
239
|
/* @__PURE__ */ v("div", {
|
|
213
240
|
className: "grid gap-1.5",
|
|
214
|
-
children: [/* @__PURE__ */ _(
|
|
241
|
+
children: [/* @__PURE__ */ _(S, {
|
|
215
242
|
htmlFor: "assessmentTitle",
|
|
216
243
|
children: "Title *"
|
|
217
|
-
}), /* @__PURE__ */ _(
|
|
244
|
+
}), /* @__PURE__ */ _(x, {
|
|
218
245
|
id: "assessmentTitle",
|
|
219
|
-
value:
|
|
220
|
-
onChange: (e) =>
|
|
221
|
-
...
|
|
246
|
+
value: V.title,
|
|
247
|
+
onChange: (e) => H({
|
|
248
|
+
...V,
|
|
222
249
|
title: e.target.value
|
|
223
250
|
}),
|
|
224
251
|
placeholder: "e.g. Module 1 knowledge check"
|
|
@@ -226,14 +253,14 @@ var re = [
|
|
|
226
253
|
}),
|
|
227
254
|
/* @__PURE__ */ v("div", {
|
|
228
255
|
className: "grid gap-1.5",
|
|
229
|
-
children: [/* @__PURE__ */ _(
|
|
256
|
+
children: [/* @__PURE__ */ _(S, {
|
|
230
257
|
htmlFor: "assessmentDescription",
|
|
231
258
|
children: "Description"
|
|
232
|
-
}), /* @__PURE__ */ _(
|
|
259
|
+
}), /* @__PURE__ */ _(j, {
|
|
233
260
|
id: "assessmentDescription",
|
|
234
|
-
value:
|
|
235
|
-
onChange: (e) =>
|
|
236
|
-
...
|
|
261
|
+
value: V.description,
|
|
262
|
+
onChange: (e) => H({
|
|
263
|
+
...V,
|
|
237
264
|
description: e.target.value
|
|
238
265
|
}),
|
|
239
266
|
placeholder: "What this assessment covers and how it's graded.",
|
|
@@ -244,37 +271,37 @@ var re = [
|
|
|
244
271
|
className: "grid gap-2 sm:grid-cols-2",
|
|
245
272
|
children: [/* @__PURE__ */ v("div", {
|
|
246
273
|
className: "grid gap-1.5",
|
|
247
|
-
children: [/* @__PURE__ */ _(
|
|
274
|
+
children: [/* @__PURE__ */ _(S, {
|
|
248
275
|
htmlFor: "assessmentType",
|
|
249
276
|
children: "Type"
|
|
250
|
-
}), /* @__PURE__ */ v(
|
|
251
|
-
value:
|
|
252
|
-
onValueChange: (e) =>
|
|
253
|
-
...
|
|
277
|
+
}), /* @__PURE__ */ v(T, {
|
|
278
|
+
value: V.type,
|
|
279
|
+
onValueChange: (e) => H({
|
|
280
|
+
...V,
|
|
254
281
|
type: e
|
|
255
282
|
}),
|
|
256
|
-
children: [/* @__PURE__ */ _(
|
|
283
|
+
children: [/* @__PURE__ */ _(O, {
|
|
257
284
|
id: "assessmentType",
|
|
258
|
-
children: /* @__PURE__ */ _(
|
|
259
|
-
}), /* @__PURE__ */ _(
|
|
285
|
+
children: /* @__PURE__ */ _(k, {})
|
|
286
|
+
}), /* @__PURE__ */ _(E, { children: re.map((e) => /* @__PURE__ */ _(D, {
|
|
260
287
|
value: e,
|
|
261
|
-
children:
|
|
288
|
+
children: ee(e)
|
|
262
289
|
}, e)) })]
|
|
263
290
|
})]
|
|
264
291
|
}), /* @__PURE__ */ v("div", {
|
|
265
292
|
className: "grid gap-1.5",
|
|
266
|
-
children: [/* @__PURE__ */ _(
|
|
293
|
+
children: [/* @__PURE__ */ _(S, {
|
|
267
294
|
htmlFor: "passingScore",
|
|
268
295
|
children: "Passing score (%)"
|
|
269
|
-
}), /* @__PURE__ */ _(
|
|
296
|
+
}), /* @__PURE__ */ _(x, {
|
|
270
297
|
id: "passingScore",
|
|
271
298
|
type: "number",
|
|
272
299
|
min: "0",
|
|
273
300
|
max: "100",
|
|
274
301
|
step: "1",
|
|
275
|
-
value:
|
|
276
|
-
onChange: (e) =>
|
|
277
|
-
...
|
|
302
|
+
value: V.passingScore,
|
|
303
|
+
onChange: (e) => H({
|
|
304
|
+
...V,
|
|
278
305
|
passingScore: e.target.value
|
|
279
306
|
}),
|
|
280
307
|
placeholder: "70"
|
|
@@ -285,34 +312,34 @@ var re = [
|
|
|
285
312
|
className: "grid gap-2 sm:grid-cols-2",
|
|
286
313
|
children: [/* @__PURE__ */ v("div", {
|
|
287
314
|
className: "grid gap-1.5",
|
|
288
|
-
children: [/* @__PURE__ */ _(
|
|
315
|
+
children: [/* @__PURE__ */ _(S, {
|
|
289
316
|
htmlFor: "maxAttempts",
|
|
290
317
|
children: "Max attempts"
|
|
291
|
-
}), /* @__PURE__ */ _(
|
|
318
|
+
}), /* @__PURE__ */ _(x, {
|
|
292
319
|
id: "maxAttempts",
|
|
293
320
|
type: "number",
|
|
294
321
|
min: "1",
|
|
295
322
|
step: "1",
|
|
296
|
-
value:
|
|
297
|
-
onChange: (e) =>
|
|
298
|
-
...
|
|
323
|
+
value: V.maxAttempts,
|
|
324
|
+
onChange: (e) => H({
|
|
325
|
+
...V,
|
|
299
326
|
maxAttempts: e.target.value
|
|
300
327
|
}),
|
|
301
328
|
placeholder: "1"
|
|
302
329
|
})]
|
|
303
330
|
}), /* @__PURE__ */ v("div", {
|
|
304
331
|
className: "grid gap-1.5",
|
|
305
|
-
children: [/* @__PURE__ */ _(
|
|
332
|
+
children: [/* @__PURE__ */ _(S, {
|
|
306
333
|
htmlFor: "timeLimitMin",
|
|
307
334
|
children: "Time limit (minutes)"
|
|
308
|
-
}), /* @__PURE__ */ _(
|
|
335
|
+
}), /* @__PURE__ */ _(x, {
|
|
309
336
|
id: "timeLimitMin",
|
|
310
337
|
type: "number",
|
|
311
338
|
min: "0",
|
|
312
339
|
step: "1",
|
|
313
|
-
value:
|
|
314
|
-
onChange: (e) =>
|
|
315
|
-
...
|
|
340
|
+
value: V.timeLimitMin,
|
|
341
|
+
onChange: (e) => H({
|
|
342
|
+
...V,
|
|
316
343
|
timeLimitMin: e.target.value
|
|
317
344
|
}),
|
|
318
345
|
placeholder: "0 = no limit"
|
|
@@ -323,7 +350,7 @@ var re = [
|
|
|
323
350
|
className: "flex items-center justify-between rounded-lg border border-border-seam bg-bg-surface px-3 py-2.5",
|
|
324
351
|
children: [/* @__PURE__ */ v("div", {
|
|
325
352
|
className: "min-w-0",
|
|
326
|
-
children: [/* @__PURE__ */ _(
|
|
353
|
+
children: [/* @__PURE__ */ _(S, {
|
|
327
354
|
htmlFor: "isGraded",
|
|
328
355
|
className: "cursor-pointer",
|
|
329
356
|
children: "Graded"
|
|
@@ -331,53 +358,53 @@ var re = [
|
|
|
331
358
|
className: "text-xs text-text-secondary",
|
|
332
359
|
children: "Contributes to the learner's score and pass/fail outcome."
|
|
333
360
|
})]
|
|
334
|
-
}), /* @__PURE__ */ _(
|
|
361
|
+
}), /* @__PURE__ */ _(A, {
|
|
335
362
|
id: "isGraded",
|
|
336
|
-
checked:
|
|
337
|
-
onCheckedChange: (e) =>
|
|
338
|
-
...
|
|
363
|
+
checked: V.isGraded,
|
|
364
|
+
onCheckedChange: (e) => H({
|
|
365
|
+
...V,
|
|
339
366
|
isGraded: e
|
|
340
367
|
})
|
|
341
368
|
})]
|
|
342
369
|
}),
|
|
343
|
-
|
|
370
|
+
U && /* @__PURE__ */ _("p", {
|
|
344
371
|
className: "text-sm text-status-error-text",
|
|
345
|
-
children:
|
|
372
|
+
children: U
|
|
346
373
|
})
|
|
347
374
|
]
|
|
348
375
|
}),
|
|
349
|
-
/* @__PURE__ */ v(
|
|
376
|
+
/* @__PURE__ */ v(oe, { children: [/* @__PURE__ */ _(y, {
|
|
350
377
|
variant: "outline",
|
|
351
378
|
onClick: $,
|
|
352
379
|
children: "Cancel"
|
|
353
380
|
}), /* @__PURE__ */ _(y, {
|
|
354
381
|
onClick: async () => {
|
|
355
|
-
if (!
|
|
356
|
-
|
|
382
|
+
if (!F) {
|
|
383
|
+
W("Pick a course first.");
|
|
357
384
|
return;
|
|
358
385
|
}
|
|
359
|
-
if (!
|
|
360
|
-
|
|
386
|
+
if (!V.title.trim()) {
|
|
387
|
+
W("Title is required.");
|
|
361
388
|
return;
|
|
362
389
|
}
|
|
363
390
|
try {
|
|
364
|
-
let e = await
|
|
365
|
-
courseId:
|
|
366
|
-
title:
|
|
367
|
-
description:
|
|
368
|
-
type:
|
|
369
|
-
passingScore:
|
|
370
|
-
maxAttempts:
|
|
371
|
-
timeLimitMin:
|
|
372
|
-
isGraded:
|
|
391
|
+
let e = await q.mutateAsync({
|
|
392
|
+
courseId: F,
|
|
393
|
+
title: V.title.trim(),
|
|
394
|
+
description: V.description.trim() || void 0,
|
|
395
|
+
type: V.type,
|
|
396
|
+
passingScore: V.passingScore ? Number(V.passingScore) : void 0,
|
|
397
|
+
maxAttempts: V.maxAttempts ? Number(V.maxAttempts) : void 0,
|
|
398
|
+
timeLimitMin: V.timeLimitMin ? Number(V.timeLimitMin) : void 0,
|
|
399
|
+
isGraded: V.isGraded
|
|
373
400
|
});
|
|
374
|
-
$(),
|
|
401
|
+
$(), P(`assessments/${e.id}`);
|
|
375
402
|
} catch (e) {
|
|
376
|
-
|
|
403
|
+
W(e instanceof Error ? e.message : "Failed to create assessment.");
|
|
377
404
|
}
|
|
378
405
|
},
|
|
379
|
-
disabled:
|
|
380
|
-
children:
|
|
406
|
+
disabled: q.isPending,
|
|
407
|
+
children: q.isPending ? "Creating…" : "Create assessment"
|
|
381
408
|
})] })
|
|
382
409
|
] })
|
|
383
410
|
})
|
|
@@ -385,6 +412,6 @@ var re = [
|
|
|
385
412
|
});
|
|
386
413
|
};
|
|
387
414
|
//#endregion
|
|
388
|
-
export {
|
|
415
|
+
export { P as AssessmentsPage };
|
|
389
416
|
|
|
390
417
|
//# sourceMappingURL=AssessmentsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssessmentsPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/AssessmentsPage.tsx"],"sourcesContent":["/**\n * AssessmentsPage — the assessment authoring surface (primary page of the section).\n * @module buildmyiq/pages/assessments\n *\n * `buiListAssessments` REQUIRES a courseId, so this page is course-scoped: the\n * educator first picks a course, then sees its assessments filtered by status.\n * Everything is real `bui*` data — no fabrication. Create flow wires\n * `useBuiCreateAssessment`.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Plus, ClipboardList } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Switch,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n FilterTabs,\n DataTable,\n EmptyState,\n LoadingView,\n ErrorView,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiCourses, useBuiAssessments, useBuiCreateAssessment } from '../../hooks';\nimport { formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport { ASSESSMENT_TYPES } from '../../types';\nimport type { BuiAssessment, BuiAssessmentStatus, BuiAssessmentType } from '../../types';\n\ntype StatusFilter = 'ALL' | BuiAssessmentStatus;\n\nconst STATUS_TABS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\ninterface AssessmentFormState {\n title: string;\n description: string;\n type: BuiAssessmentType;\n passingScore: string;\n maxAttempts: string;\n timeLimitMin: string;\n isGraded: boolean;\n}\n\nconst makeEmptyForm = (): AssessmentFormState => ({\n title: '',\n description: '',\n type: 'QUIZ',\n passingScore: '70',\n maxAttempts: '1',\n timeLimitMin: '0',\n isGraded: true,\n});\n\nexport const AssessmentsPage: FC = () => {\n const { navigateTo } = useBuildmyiq();\n\n const [courseId, setCourseId] = useState<string>('');\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<AssessmentFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const coursesQuery = useBuiCourses({ limit: 100 });\n // Fetch the full (unfiltered) list so the tab counts stay accurate; filter client-side.\n const assessmentsQuery = useBuiAssessments(courseId || undefined);\n const createAssessment = useBuiCreateAssessment();\n\n const courses = coursesQuery.data?.items ?? [];\n const assessments = useMemo(() => assessmentsQuery.data ?? [], [assessmentsQuery.data]);\n\n const statusCounts = useMemo(() => {\n const counts: Record<StatusFilter, number> = {\n ALL: assessments.length,\n DRAFT: 0,\n PUBLISHED: 0,\n ARCHIVED: 0,\n };\n for (const a of assessments) counts[a.status] += 1;\n return counts;\n }, [assessments]);\n\n const rows = useMemo(\n () => (status === 'ALL' ? assessments : assessments.filter((a) => a.status === status)),\n [assessments, status]\n );\n\n const tabOptions: FilterOption<StatusFilter>[] = STATUS_TABS.map((t) => ({\n value: t.value,\n label: t.label,\n count: statusCounts[t.value],\n }));\n\n const columns: Column<BuiAssessment>[] = [\n {\n key: 'title',\n header: 'Assessment',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"font-semibold text-text-primary\">{a.title}</p>\n {a.description && (\n <p\n className=\"mt-0.5 max-w-md truncate text-xs text-text-secondary\"\n title={a.description}\n >\n {a.description}\n </p>\n )}\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n render: (a) => <StatusBadge status={a.type} />,\n },\n {\n key: 'status',\n header: 'Status',\n sortValue: (a) => a.status,\n render: (a) => <StatusBadge status={a.status} />,\n },\n {\n key: 'passingScore',\n header: 'Pass',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.passingScore,\n render: (a) => <span className=\"tabular-nums text-text-secondary\">{a.passingScore}%</span>,\n },\n {\n key: 'questions',\n header: 'Questions',\n align: 'right',\n hideOnMobile: true,\n render: (a) => (\n <span className=\"tabular-nums text-text-secondary\">{a.questions?.length ?? '—'}</span>\n ),\n },\n {\n key: 'updatedAt',\n header: 'Updated',\n hideOnMobile: true,\n sortValue: (a) => a.updatedAt,\n render: (a) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatRelativeTime(a.updatedAt)}\n </span>\n ),\n },\n ];\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!courseId) {\n setFormError('Pick a course first.');\n return;\n }\n if (!form.title.trim()) {\n setFormError('Title is required.');\n return;\n }\n try {\n const created = await createAssessment.mutateAsync({\n courseId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n type: form.type,\n passingScore: form.passingScore ? Number(form.passingScore) : undefined,\n maxAttempts: form.maxAttempts ? Number(form.maxAttempts) : undefined,\n timeLimitMin: form.timeLimitMin ? Number(form.timeLimitMin) : undefined,\n isGraded: form.isGraded,\n });\n closeDialog();\n navigateTo(`assessments/${created.id}`);\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create assessment.');\n }\n };\n\n return (\n <PageLayout\n title=\"Assessments\"\n description=\"Quizzes, exams and graded assignments for every course in your workspace.\"\n titleGradient\n aurora\n actions={\n <Button onClick={openDialog} disabled={!courseId}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New assessment\n </Button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"mb-5 p-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end\">\n <div className=\"grid gap-1.5\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Course\n </span>\n <Select\n value={courseId}\n onValueChange={(v) => {\n setCourseId(v);\n setStatus('ALL');\n }}\n >\n <SelectTrigger className=\"w-full sm:w-72\">\n <SelectValue placeholder=\"Select a course\" />\n </SelectTrigger>\n <SelectContent>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {coursesQuery.isSuccess && courses.length === 0 && (\n <p className=\"text-xs text-text-secondary\">\n No courses yet — create one in Authoring first.\n </p>\n )}\n </div>\n {courseId && (\n <div className=\"grid gap-1.5\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Status\n </span>\n <FilterTabs options={tabOptions} value={status} onChange={setStatus} />\n </div>\n )}\n </div>\n </GlassCard>\n\n {!courseId ? (\n <EmptyState\n illustration=\"onboarding\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title=\"Pick a course to see its assessments\"\n description=\"Assessments are scoped to a course. Choose a course above to view, filter and create its quizzes and exams.\"\n />\n ) : assessmentsQuery.isLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsQuery.isError ? (\n <ErrorView\n message=\"Couldn't load assessments for this course.\"\n onRetry={() => assessmentsQuery.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title={status === 'ALL' ? 'No assessments yet' : 'No assessments match this filter'}\n description={\n status === 'ALL'\n ? 'Create the first quiz, exam or assignment for this course.'\n : 'Try a different status filter, or create a new assessment.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New assessment\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(a) => a.id}\n onRowClick={(a) => navigateTo(`assessments/${a.id}`)}\n defaultSort={{ key: 'updatedAt', dir: 'desc' }}\n />\n )}\n\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New assessment</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Add a quiz, exam or assignment to this course. You can add questions after it's\n created.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assessmentTitle\">Title *</Label>\n <Input\n id=\"assessmentTitle\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n placeholder=\"e.g. Module 1 knowledge check\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assessmentDescription\">Description</Label>\n <Textarea\n id=\"assessmentDescription\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"What this assessment covers and how it's graded.\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assessmentType\">Type</Label>\n <Select\n value={form.type}\n onValueChange={(v) => setForm({ ...form, type: v as BuiAssessmentType })}\n >\n <SelectTrigger id=\"assessmentType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ASSESSMENT_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"passingScore\">Passing score (%)</Label>\n <Input\n id=\"passingScore\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n value={form.passingScore}\n onChange={(e) => setForm({ ...form, passingScore: e.target.value })}\n placeholder=\"70\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"maxAttempts\">Max attempts</Label>\n <Input\n id=\"maxAttempts\"\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={form.maxAttempts}\n onChange={(e) => setForm({ ...form, maxAttempts: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"timeLimitMin\">Time limit (minutes)</Label>\n <Input\n id=\"timeLimitMin\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={form.timeLimitMin}\n onChange={(e) => setForm({ ...form, timeLimitMin: e.target.value })}\n placeholder=\"0 = no limit\"\n />\n </div>\n </div>\n\n <div className=\"flex items-center justify-between rounded-lg border border-border-seam bg-bg-surface px-3 py-2.5\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"isGraded\" className=\"cursor-pointer\">\n Graded\n </Label>\n <p className=\"text-xs text-text-secondary\">\n Contributes to the learner's score and pass/fail outcome.\n </p>\n </div>\n <Switch\n id=\"isGraded\"\n checked={form.isGraded}\n onCheckedChange={(checked) => setForm({ ...form, isGraded: checked })}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button onClick={handleCreate} disabled={createAssessment.isPending}>\n {createAssessment.isPending ? 'Creating…' : 'Create assessment'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssessmentsPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/AssessmentsPage.tsx"],"sourcesContent":["/**\n * AssessmentsPage — the assessment authoring surface (primary page of the section).\n * @module buildmyiq/pages/assessments\n *\n * `buiListAssessments` REQUIRES a courseId, so this page is course-scoped: the\n * educator first picks a course, then sees its assessments filtered by status.\n * Everything is real `bui*` data — no fabrication. Create flow wires\n * `useBuiCreateAssessment`.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Plus, ClipboardList, CheckCircle2, FileEdit, ListChecks } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Switch,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n StatusBadge,\n FilterTabs,\n DataTable,\n EmptyState,\n LoadingView,\n ErrorView,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiCourses, useBuiAssessments, useBuiCreateAssessment } from '../../hooks';\nimport { formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport { ASSESSMENT_TYPES } from '../../types';\nimport type { BuiAssessment, BuiAssessmentStatus, BuiAssessmentType } from '../../types';\n\ntype StatusFilter = 'ALL' | BuiAssessmentStatus;\n\nconst STATUS_TABS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\ninterface AssessmentFormState {\n title: string;\n description: string;\n type: BuiAssessmentType;\n passingScore: string;\n maxAttempts: string;\n timeLimitMin: string;\n isGraded: boolean;\n}\n\nconst makeEmptyForm = (): AssessmentFormState => ({\n title: '',\n description: '',\n type: 'QUIZ',\n passingScore: '70',\n maxAttempts: '1',\n timeLimitMin: '0',\n isGraded: true,\n});\n\nexport const AssessmentsPage: FC = () => {\n const { navigateTo } = useBuildmyiq();\n\n const [courseId, setCourseId] = useState<string>('');\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<AssessmentFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const coursesQuery = useBuiCourses({ limit: 100 });\n // Fetch the full (unfiltered) list so the tab counts stay accurate; filter client-side.\n const assessmentsQuery = useBuiAssessments(courseId || undefined);\n const createAssessment = useBuiCreateAssessment();\n\n const courses = coursesQuery.data?.items ?? [];\n const assessments = useMemo(() => assessmentsQuery.data ?? [], [assessmentsQuery.data]);\n\n const statusCounts = useMemo(() => {\n const counts: Record<StatusFilter, number> = {\n ALL: assessments.length,\n DRAFT: 0,\n PUBLISHED: 0,\n ARCHIVED: 0,\n };\n for (const a of assessments) counts[a.status] += 1;\n return counts;\n }, [assessments]);\n\n const rows = useMemo(\n () => (status === 'ALL' ? assessments : assessments.filter((a) => a.status === status)),\n [assessments, status]\n );\n\n const totalQuestions = useMemo(\n () => assessments.reduce((sum, a) => sum + (a.questions?.length ?? 0), 0),\n [assessments]\n );\n\n const tabOptions: FilterOption<StatusFilter>[] = STATUS_TABS.map((t) => ({\n value: t.value,\n label: t.label,\n count: statusCounts[t.value],\n }));\n\n const columns: Column<BuiAssessment>[] = [\n {\n key: 'title',\n header: 'Assessment',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"font-semibold text-text-primary\">{a.title}</p>\n {a.description && (\n <p\n className=\"mt-0.5 max-w-md truncate text-xs text-text-secondary\"\n title={a.description}\n >\n {a.description}\n </p>\n )}\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n render: (a) => <StatusBadge status={a.type} />,\n },\n {\n key: 'status',\n header: 'Status',\n sortValue: (a) => a.status,\n render: (a) => <StatusBadge status={a.status} />,\n },\n {\n key: 'passingScore',\n header: 'Pass',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.passingScore,\n render: (a) => <span className=\"tabular-nums text-text-secondary\">{a.passingScore}%</span>,\n },\n {\n key: 'questions',\n header: 'Questions',\n align: 'right',\n hideOnMobile: true,\n render: (a) => (\n <span className=\"tabular-nums text-text-secondary\">{a.questions?.length ?? '—'}</span>\n ),\n },\n {\n key: 'updatedAt',\n header: 'Updated',\n hideOnMobile: true,\n sortValue: (a) => a.updatedAt,\n render: (a) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatRelativeTime(a.updatedAt)}\n </span>\n ),\n },\n ];\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!courseId) {\n setFormError('Pick a course first.');\n return;\n }\n if (!form.title.trim()) {\n setFormError('Title is required.');\n return;\n }\n try {\n const created = await createAssessment.mutateAsync({\n courseId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n type: form.type,\n passingScore: form.passingScore ? Number(form.passingScore) : undefined,\n maxAttempts: form.maxAttempts ? Number(form.maxAttempts) : undefined,\n timeLimitMin: form.timeLimitMin ? Number(form.timeLimitMin) : undefined,\n isGraded: form.isGraded,\n });\n closeDialog();\n navigateTo(`assessments/${created.id}`);\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create assessment.');\n }\n };\n\n return (\n <PageLayout\n title=\"Assessments\"\n description=\"Quizzes, exams and graded assignments for every course in your workspace.\"\n titleGradient\n aurora\n actions={\n <Button onClick={openDialog} disabled={!courseId}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New assessment\n </Button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"mb-5 p-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end\">\n <div className=\"grid gap-1.5\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Course\n </span>\n <Select\n value={courseId}\n onValueChange={(v) => {\n setCourseId(v);\n setStatus('ALL');\n }}\n >\n <SelectTrigger className=\"w-full sm:w-72\">\n <SelectValue placeholder=\"Select a course\" />\n </SelectTrigger>\n <SelectContent>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {coursesQuery.isSuccess && courses.length === 0 && (\n <p className=\"text-xs text-text-secondary\">\n No courses yet — create one in Authoring first.\n </p>\n )}\n </div>\n {courseId && (\n <div className=\"grid gap-1.5\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Status\n </span>\n <FilterTabs options={tabOptions} value={status} onChange={setStatus} />\n </div>\n )}\n </div>\n </GlassCard>\n\n {courseId && assessmentsQuery.isSuccess && assessments.length > 0 && (\n <div className=\"mb-5 grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<ClipboardList className=\"h-5 w-5\" />}\n value={statusCounts.ALL}\n label=\"Assessments\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={statusCounts.PUBLISHED}\n label=\"Published\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<FileEdit className=\"h-5 w-5\" />}\n value={statusCounts.DRAFT}\n label=\"Drafts\"\n />\n <MetricsCard\n icon={<ListChecks className=\"h-5 w-5\" />}\n value={totalQuestions}\n label=\"Questions\"\n />\n </div>\n )}\n\n {!courseId ? (\n <EmptyState\n illustration=\"onboarding\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title=\"Pick a course to see its assessments\"\n description=\"Assessments are scoped to a course. Choose a course above to view, filter and create its quizzes and exams.\"\n />\n ) : assessmentsQuery.isLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsQuery.isError ? (\n <ErrorView\n message=\"Couldn't load assessments for this course.\"\n onRetry={() => assessmentsQuery.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title={status === 'ALL' ? 'No assessments yet' : 'No assessments match this filter'}\n description={\n status === 'ALL'\n ? 'Create the first quiz, exam or assignment for this course.'\n : 'Try a different status filter, or create a new assessment.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New assessment\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(a) => a.id}\n onRowClick={(a) => navigateTo(`assessments/${a.id}`)}\n defaultSort={{ key: 'updatedAt', dir: 'desc' }}\n />\n )}\n\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New assessment</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Add a quiz, exam or assignment to this course. You can add questions after it's\n created.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assessmentTitle\">Title *</Label>\n <Input\n id=\"assessmentTitle\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n placeholder=\"e.g. Module 1 knowledge check\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assessmentDescription\">Description</Label>\n <Textarea\n id=\"assessmentDescription\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"What this assessment covers and how it's graded.\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assessmentType\">Type</Label>\n <Select\n value={form.type}\n onValueChange={(v) => setForm({ ...form, type: v as BuiAssessmentType })}\n >\n <SelectTrigger id=\"assessmentType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ASSESSMENT_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"passingScore\">Passing score (%)</Label>\n <Input\n id=\"passingScore\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n value={form.passingScore}\n onChange={(e) => setForm({ ...form, passingScore: e.target.value })}\n placeholder=\"70\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"maxAttempts\">Max attempts</Label>\n <Input\n id=\"maxAttempts\"\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={form.maxAttempts}\n onChange={(e) => setForm({ ...form, maxAttempts: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"timeLimitMin\">Time limit (minutes)</Label>\n <Input\n id=\"timeLimitMin\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={form.timeLimitMin}\n onChange={(e) => setForm({ ...form, timeLimitMin: e.target.value })}\n placeholder=\"0 = no limit\"\n />\n </div>\n </div>\n\n <div className=\"flex items-center justify-between rounded-lg border border-border-seam bg-bg-surface px-3 py-2.5\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"isGraded\" className=\"cursor-pointer\">\n Graded\n </Label>\n <p className=\"text-xs text-text-secondary\">\n Contributes to the learner's score and pass/fail outcome.\n </p>\n </div>\n <Switch\n id=\"isGraded\"\n checked={form.isGraded}\n onCheckedChange={(checked) => setForm({ ...form, isGraded: checked })}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button onClick={handleCreate} disabled={createAssessment.isPending}>\n {createAssessment.isPending ? 'Creating…' : 'Create assessment'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TakeAssessmentPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"TakeAssessmentPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AA8D3C,eAAO,MAAM,kBAAkB,EAAE,EA+VhC,CAAC"}
|