@burdenoff/microfe-buildmyiq 2026.705.8 → 2026.705.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 +5 -0
- package/dist/buildmyiq/BuildmyiqRoot.d.ts.map +1 -0
- package/dist/buildmyiq/BuildmyiqRoot.js +54 -46
- package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoutes.d.ts +4 -0
- package/dist/buildmyiq/BuildmyiqRoutes.d.ts.map +1 -0
- package/dist/buildmyiq/BuildmyiqRoutes.js +19 -11
- package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
- package/dist/buildmyiq/components/BuiDiscussion.d.ts +18 -0
- package/dist/buildmyiq/components/BuiDiscussion.d.ts.map +1 -0
- package/dist/buildmyiq/components/BuiTags.d.ts +17 -0
- package/dist/buildmyiq/components/BuiTags.d.ts.map +1 -0
- package/dist/buildmyiq/components/DataTable.d.ts +28 -0
- package/dist/buildmyiq/components/DataTable.d.ts.map +1 -0
- package/dist/buildmyiq/components/DataTable.js +1 -1
- package/dist/buildmyiq/components/DataTable.js.map +1 -1
- package/dist/buildmyiq/components/EmptyState.d.ts +18 -0
- package/dist/buildmyiq/components/EmptyState.d.ts.map +1 -0
- package/dist/buildmyiq/components/EvidenceUpload.d.ts +14 -0
- package/dist/buildmyiq/components/EvidenceUpload.d.ts.map +1 -0
- package/dist/buildmyiq/components/FileLink.d.ts +15 -0
- package/dist/buildmyiq/components/FileLink.d.ts.map +1 -0
- package/dist/buildmyiq/components/FilterTabs.d.ts +18 -0
- package/dist/buildmyiq/components/FilterTabs.d.ts.map +1 -0
- package/dist/buildmyiq/components/MetricsCard.d.ts +23 -0
- package/dist/buildmyiq/components/MetricsCard.d.ts.map +1 -0
- package/dist/buildmyiq/components/MiniBar.d.ts +16 -0
- package/dist/buildmyiq/components/MiniBar.d.ts.map +1 -0
- package/dist/buildmyiq/components/MiniBar.js +1 -1
- package/dist/buildmyiq/components/MiniBar.js.map +1 -1
- package/dist/buildmyiq/components/PageLayout.d.ts +16 -0
- package/dist/buildmyiq/components/PageLayout.d.ts.map +1 -0
- package/dist/buildmyiq/components/Pagination.d.ts +22 -0
- package/dist/buildmyiq/components/Pagination.d.ts.map +1 -0
- package/dist/buildmyiq/components/StateViews.d.ts +24 -0
- package/dist/buildmyiq/components/StateViews.d.ts.map +1 -0
- package/dist/buildmyiq/components/StatusBadge.d.ts +9 -0
- package/dist/buildmyiq/components/StatusBadge.d.ts.map +1 -0
- package/dist/buildmyiq/components/Tabs.d.ts +17 -0
- package/dist/buildmyiq/components/Tabs.d.ts.map +1 -0
- package/dist/buildmyiq/components/index.d.ts +19 -0
- package/dist/buildmyiq/components/index.d.ts.map +1 -0
- package/dist/buildmyiq/context/BuildmyiqContext.d.ts +16 -0
- package/dist/buildmyiq/context/BuildmyiqContext.d.ts.map +1 -0
- package/dist/buildmyiq/context/index.d.ts +6 -0
- package/dist/buildmyiq/context/index.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/index.d.ts +10 -0
- package/dist/buildmyiq/hooks/index.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/useBuiComments.d.ts +36 -0
- package/dist/buildmyiq/hooks/useBuiComments.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts +47 -0
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/useBuiTags.d.ts +44 -0
- package/dist/buildmyiq/hooks/useBuiTags.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts +65 -0
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js +23 -15
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
- package/dist/buildmyiq/hooks/useCursorPager.d.ts +33 -0
- package/dist/buildmyiq/hooks/useCursorPager.d.ts.map +1 -0
- package/dist/buildmyiq/index.d.ts +9 -0
- package/dist/buildmyiq/index.d.ts.map +1 -0
- package/dist/buildmyiq/pages/DashboardPage.d.ts +4 -0
- package/dist/buildmyiq/pages/DashboardPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/DashboardPage.js +2 -2
- package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
- package/dist/buildmyiq/pages/_shared/CourseCard.d.ts +13 -0
- package/dist/buildmyiq/pages/_shared/CourseCard.d.ts.map +1 -0
- package/dist/buildmyiq/pages/_shared/index.d.ts +6 -0
- package/dist/buildmyiq/pages/_shared/index.d.ts.map +1 -0
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts +4 -0
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts +4 -0
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +264 -0
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -0
- package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts +4 -0
- package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js +4 -4
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts +4 -0
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/authoring/_forms.d.ts +44 -0
- package/dist/buildmyiq/pages/authoring/_forms.d.ts.map +1 -0
- package/dist/buildmyiq/pages/authoring/_forms.js +15 -15
- package/dist/buildmyiq/pages/authoring/_forms.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts +4 -0
- package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts +4 -0
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +9 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts +4 -0
- package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
- package/dist/buildmyiq/pages/insights/InsightsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/insights/InsightsPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/insights/InsightsPage.js +242 -0
- package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -0
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts +4 -0
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts +4 -0
- package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/learning/MyLearningPage.js +83 -77
- package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
- package/dist/buildmyiq/pages/skills/SkillsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -0
- package/dist/buildmyiq/types/index.d.ts +382 -0
- package/dist/buildmyiq/types/index.d.ts.map +1 -0
- package/dist/buildmyiq/utils/cn.d.ts +4 -0
- package/dist/buildmyiq/utils/cn.d.ts.map +1 -0
- package/dist/buildmyiq/utils/formatters.d.ts +33 -0
- package/dist/buildmyiq/utils/formatters.d.ts.map +1 -0
- package/dist/buildmyiq/utils/i18n.d.ts +6 -0
- package/dist/buildmyiq/utils/i18n.d.ts.map +1 -0
- package/dist/buildmyiq/utils/index.d.ts +9 -0
- package/dist/buildmyiq/utils/index.d.ts.map +1 -0
- package/dist/buildmyiq/utils/navigation.d.ts +14 -0
- package/dist/buildmyiq/utils/navigation.d.ts.map +1 -0
- package/dist/dev/main.d.ts +2 -0
- package/dist/dev/main.d.ts.map +1 -0
- package/dist/generated/global-operations.d.ts +2 -0
- package/dist/generated/global-operations.d.ts.map +1 -0
- package/dist/generated/global-types.d.ts +18199 -0
- package/dist/generated/global-types.d.ts.map +1 -0
- package/dist/generated/wspace-operations.d.ts +2 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -0
- package/dist/generated/wspace-types.d.ts +43046 -0
- package/dist/generated/wspace-types.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useBuildmyiq as e } from "../../context/BuildmyiqContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { formatDuration as r } from "../../utils/formatters.js";
|
|
5
|
+
import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
6
|
+
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
7
|
+
import { ErrorView as o, LoadingView as s } from "../../components/StateViews.js";
|
|
8
|
+
import "../../context/index.js";
|
|
9
|
+
import { useBuiAssessment as c, useBuiEnrollments as l, useBuiSubmitAssessment as u } from "../../hooks/useBuildmyiqApi.js";
|
|
10
|
+
import "../../hooks/index.js";
|
|
11
|
+
import "../../components/index.js";
|
|
12
|
+
import { useMemo as d, useState as f } from "react";
|
|
13
|
+
import { ArrowLeft as p, CheckCircle2 as m, ClipboardCheck as h, Send as g } from "lucide-react";
|
|
14
|
+
import { useParams as _ } from "react-router-dom";
|
|
15
|
+
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
16
|
+
import { Button as b, GlassCard as x, Input as S, Textarea as C } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
//#region src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx
|
|
18
|
+
function w(e) {
|
|
19
|
+
return Array.isArray(e) ? e.map((e) => e && typeof e == "object" && "label" in e ? { label: String(e.label) } : null).filter((e) => e !== null && e.label.length > 0) : [];
|
|
20
|
+
}
|
|
21
|
+
var T = [{ label: "True" }, { label: "False" }], E = () => {
|
|
22
|
+
let { assessmentId: E } = _(), { currentUser: D, navigateTo: O } = e(), k = c(E), A = k.data ?? null, j = l({
|
|
23
|
+
learnerId: D?.id,
|
|
24
|
+
courseId: A?.courseId,
|
|
25
|
+
enabled: !!(D?.id && A?.courseId)
|
|
26
|
+
}).data?.items?.[0]?.id, M = u(), [N, P] = f({}), [F, I] = f(null), L = d(() => [...A?.questions ?? []].sort((e, t) => e.position - t.position), [A]), R = (e, t) => {
|
|
27
|
+
P((n) => ({
|
|
28
|
+
...n,
|
|
29
|
+
[e]: t
|
|
30
|
+
}));
|
|
31
|
+
}, z = (e, t) => {
|
|
32
|
+
P((n) => {
|
|
33
|
+
let r = Array.isArray(n[e]) ? n[e] : [];
|
|
34
|
+
return {
|
|
35
|
+
...n,
|
|
36
|
+
[e]: r.includes(t) ? r.filter((e) => e !== t) : [...r, t]
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}, B = L.filter((e) => {
|
|
40
|
+
let t = N[e.id];
|
|
41
|
+
return Array.isArray(t) ? t.length > 0 : !!(t && String(t).trim());
|
|
42
|
+
}).length, V = async () => {
|
|
43
|
+
E && I((await M.mutateAsync({
|
|
44
|
+
assessmentId: E,
|
|
45
|
+
enrollmentId: j,
|
|
46
|
+
attempt: 1,
|
|
47
|
+
answers: N
|
|
48
|
+
})).id);
|
|
49
|
+
}, H = A ? /* @__PURE__ */ y(b, {
|
|
50
|
+
variant: "ghost",
|
|
51
|
+
size: "sm",
|
|
52
|
+
onClick: () => O(`catalog/${A.courseId}`),
|
|
53
|
+
children: [/* @__PURE__ */ v(p, { className: "mr-1 h-4 w-4" }), " Back to course"]
|
|
54
|
+
}) : void 0;
|
|
55
|
+
return E ? k.isLoading ? /* @__PURE__ */ v(n, {
|
|
56
|
+
title: "Assessment",
|
|
57
|
+
children: /* @__PURE__ */ v(s, { label: "Loading the assessment…" })
|
|
58
|
+
}) : k.isError || !A ? /* @__PURE__ */ v(n, {
|
|
59
|
+
title: "Assessment",
|
|
60
|
+
actions: H,
|
|
61
|
+
children: /* @__PURE__ */ v(o, {
|
|
62
|
+
title: "Couldn't load this assessment",
|
|
63
|
+
message: k.error instanceof Error ? k.error.message : void 0,
|
|
64
|
+
onRetry: () => k.refetch()
|
|
65
|
+
})
|
|
66
|
+
}) : A.status === "PUBLISHED" ? F ? /* @__PURE__ */ v(n, {
|
|
67
|
+
title: A.title,
|
|
68
|
+
actions: H,
|
|
69
|
+
children: /* @__PURE__ */ v("div", {
|
|
70
|
+
className: "mx-auto max-w-lg",
|
|
71
|
+
children: /* @__PURE__ */ y(x, {
|
|
72
|
+
treatment: "glass",
|
|
73
|
+
glow: !0,
|
|
74
|
+
className: "flex flex-col items-center gap-4 p-8 text-center",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ v("div", {
|
|
77
|
+
className: "flex h-14 w-14 items-center justify-center rounded-full bg-status-success-bg-subtle",
|
|
78
|
+
children: /* @__PURE__ */ v(m, { className: "h-7 w-7 text-status-success-text" })
|
|
79
|
+
}),
|
|
80
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h2", {
|
|
81
|
+
className: "text-lg font-semibold text-text-primary",
|
|
82
|
+
children: "Submitted"
|
|
83
|
+
}), /* @__PURE__ */ y("p", {
|
|
84
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
85
|
+
children: [
|
|
86
|
+
"Your answers for “",
|
|
87
|
+
A.title,
|
|
88
|
+
"” were submitted.",
|
|
89
|
+
A.isGraded ? " An educator will grade them — you’ll see your score and feedback in My Learning." : " Thanks for completing it."
|
|
90
|
+
]
|
|
91
|
+
})] }),
|
|
92
|
+
/* @__PURE__ */ y("div", {
|
|
93
|
+
className: "flex flex-wrap justify-center gap-2",
|
|
94
|
+
children: [/* @__PURE__ */ v(b, {
|
|
95
|
+
variant: "secondary",
|
|
96
|
+
onClick: () => O("learning"),
|
|
97
|
+
children: "Go to My Learning"
|
|
98
|
+
}), /* @__PURE__ */ v(b, {
|
|
99
|
+
variant: "ghost",
|
|
100
|
+
onClick: () => O(`catalog/${A.courseId}`),
|
|
101
|
+
children: "Back to course"
|
|
102
|
+
})]
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
}) : /* @__PURE__ */ v(n, {
|
|
108
|
+
title: A.title,
|
|
109
|
+
description: A.description ?? void 0,
|
|
110
|
+
actions: H,
|
|
111
|
+
children: /* @__PURE__ */ y("div", {
|
|
112
|
+
className: "mx-auto max-w-3xl",
|
|
113
|
+
children: [/* @__PURE__ */ y("div", {
|
|
114
|
+
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ v(i, { status: A.type }),
|
|
117
|
+
/* @__PURE__ */ y("span", {
|
|
118
|
+
className: "text-sm text-text-secondary",
|
|
119
|
+
children: [
|
|
120
|
+
"Passing score ",
|
|
121
|
+
A.passingScore,
|
|
122
|
+
"%"
|
|
123
|
+
]
|
|
124
|
+
}),
|
|
125
|
+
A.timeLimitMin > 0 && /* @__PURE__ */ y("span", {
|
|
126
|
+
className: "text-sm text-text-secondary",
|
|
127
|
+
children: ["· ", r(A.timeLimitMin)]
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ y("span", {
|
|
130
|
+
className: "text-sm text-text-secondary",
|
|
131
|
+
children: [
|
|
132
|
+
"· ",
|
|
133
|
+
B,
|
|
134
|
+
"/",
|
|
135
|
+
L.length,
|
|
136
|
+
" answered"
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
}), L.length === 0 ? /* @__PURE__ */ v(a, {
|
|
141
|
+
icon: /* @__PURE__ */ v(h, { className: "h-8 w-8" }),
|
|
142
|
+
title: "No questions yet",
|
|
143
|
+
description: "This assessment doesn't have any questions to answer."
|
|
144
|
+
}) : /* @__PURE__ */ y("div", {
|
|
145
|
+
className: "flex flex-col gap-4",
|
|
146
|
+
children: [
|
|
147
|
+
L.map((e, n) => {
|
|
148
|
+
let r = e.type === "TRUE_FALSE" ? T : w(e.options), i = N[e.id];
|
|
149
|
+
return /* @__PURE__ */ v(x, {
|
|
150
|
+
treatment: "plain",
|
|
151
|
+
className: "p-5",
|
|
152
|
+
children: /* @__PURE__ */ y("div", {
|
|
153
|
+
className: "flex items-start gap-3",
|
|
154
|
+
children: [/* @__PURE__ */ v("span", {
|
|
155
|
+
className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary tabular-nums",
|
|
156
|
+
children: n + 1
|
|
157
|
+
}), /* @__PURE__ */ y("div", {
|
|
158
|
+
className: "min-w-0 flex-1",
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ v("p", {
|
|
161
|
+
className: "font-medium text-text-primary",
|
|
162
|
+
children: e.prompt
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ y("p", {
|
|
165
|
+
className: "mt-0.5 text-xs text-text-secondary",
|
|
166
|
+
children: [
|
|
167
|
+
e.points,
|
|
168
|
+
" ",
|
|
169
|
+
e.points === 1 ? "point" : "points"
|
|
170
|
+
]
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ y("div", {
|
|
173
|
+
className: "mt-4",
|
|
174
|
+
children: [
|
|
175
|
+
(e.type === "SINGLE_CHOICE" || e.type === "TRUE_FALSE") && /* @__PURE__ */ v("div", {
|
|
176
|
+
className: "flex flex-col gap-2",
|
|
177
|
+
children: r.map((n) => /* @__PURE__ */ y("label", {
|
|
178
|
+
className: t("flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors", i === n.label ? "border-accent bg-[var(--color-accent-soft)] text-text-primary" : "border-border-seam text-text-secondary hover:bg-bg-sunken"),
|
|
179
|
+
children: [/* @__PURE__ */ v("input", {
|
|
180
|
+
type: "radio",
|
|
181
|
+
name: e.id,
|
|
182
|
+
className: "accent-[var(--color-accent-primary)]",
|
|
183
|
+
checked: i === n.label,
|
|
184
|
+
onChange: () => R(e.id, n.label)
|
|
185
|
+
}), n.label]
|
|
186
|
+
}, n.label))
|
|
187
|
+
}),
|
|
188
|
+
e.type === "MULTIPLE_CHOICE" && /* @__PURE__ */ v("div", {
|
|
189
|
+
className: "flex flex-col gap-2",
|
|
190
|
+
children: r.map((n) => {
|
|
191
|
+
let r = Array.isArray(i) && i.includes(n.label);
|
|
192
|
+
return /* @__PURE__ */ y("label", {
|
|
193
|
+
className: t("flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors", r ? "border-accent bg-[var(--color-accent-soft)] text-text-primary" : "border-border-seam text-text-secondary hover:bg-bg-sunken"),
|
|
194
|
+
children: [/* @__PURE__ */ v("input", {
|
|
195
|
+
type: "checkbox",
|
|
196
|
+
className: "accent-[var(--color-accent-primary)]",
|
|
197
|
+
checked: r,
|
|
198
|
+
onChange: () => z(e.id, n.label)
|
|
199
|
+
}), n.label]
|
|
200
|
+
}, n.label);
|
|
201
|
+
})
|
|
202
|
+
}),
|
|
203
|
+
e.type === "SHORT_ANSWER" && /* @__PURE__ */ v(S, {
|
|
204
|
+
value: typeof i == "string" ? i : "",
|
|
205
|
+
onChange: (t) => R(e.id, t.target.value),
|
|
206
|
+
placeholder: "Your answer"
|
|
207
|
+
}),
|
|
208
|
+
(e.type === "ESSAY" || e.type === "CODE") && /* @__PURE__ */ v(C, {
|
|
209
|
+
rows: e.type === "CODE" ? 6 : 4,
|
|
210
|
+
value: typeof i == "string" ? i : "",
|
|
211
|
+
onChange: (t) => R(e.id, t.target.value),
|
|
212
|
+
placeholder: e.type === "CODE" ? "Write your code…" : "Write your answer…",
|
|
213
|
+
className: e.type === "CODE" ? "font-mono text-sm" : void 0
|
|
214
|
+
})
|
|
215
|
+
]
|
|
216
|
+
})
|
|
217
|
+
]
|
|
218
|
+
})]
|
|
219
|
+
})
|
|
220
|
+
}, e.id);
|
|
221
|
+
}),
|
|
222
|
+
M.isError && /* @__PURE__ */ v("p", {
|
|
223
|
+
className: "text-sm text-status-error-text",
|
|
224
|
+
children: M.error instanceof Error ? M.error.message : "Could not submit your answers."
|
|
225
|
+
}),
|
|
226
|
+
/* @__PURE__ */ y("div", {
|
|
227
|
+
className: "sticky bottom-0 flex items-center justify-between gap-3 border-t border-border-seam bg-bg-surface/90 py-3 backdrop-blur",
|
|
228
|
+
children: [/* @__PURE__ */ y("span", {
|
|
229
|
+
className: "text-sm text-text-secondary tabular-nums",
|
|
230
|
+
children: [
|
|
231
|
+
B,
|
|
232
|
+
" of ",
|
|
233
|
+
L.length,
|
|
234
|
+
" answered"
|
|
235
|
+
]
|
|
236
|
+
}), /* @__PURE__ */ y(b, {
|
|
237
|
+
onClick: V,
|
|
238
|
+
disabled: M.isPending || B === 0,
|
|
239
|
+
children: [/* @__PURE__ */ v(g, { className: "mr-1 h-4 w-4" }), M.isPending ? "Submitting…" : "Submit assessment"]
|
|
240
|
+
})]
|
|
241
|
+
})
|
|
242
|
+
]
|
|
243
|
+
})]
|
|
244
|
+
})
|
|
245
|
+
}) : /* @__PURE__ */ v(n, {
|
|
246
|
+
title: A.title,
|
|
247
|
+
actions: H,
|
|
248
|
+
children: /* @__PURE__ */ v(a, {
|
|
249
|
+
illustration: "empty-data",
|
|
250
|
+
title: "This assessment isn't open yet",
|
|
251
|
+
description: "It has to be published by an educator before you can take it."
|
|
252
|
+
})
|
|
253
|
+
}) : /* @__PURE__ */ v(n, {
|
|
254
|
+
title: "Assessment",
|
|
255
|
+
children: /* @__PURE__ */ v(a, {
|
|
256
|
+
illustration: "empty-data",
|
|
257
|
+
title: "No assessment selected"
|
|
258
|
+
})
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
//#endregion
|
|
262
|
+
export { E as TakeAssessmentPage };
|
|
263
|
+
|
|
264
|
+
//# sourceMappingURL=TakeAssessmentPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TakeAssessmentPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx"],"sourcesContent":["/**\n * TakeAssessmentPage — the LEARNER-facing surface to attempt a published\n * assessment and submit answers.\n * @module buildmyiq/pages/assessments\n *\n * Loads the assessment + its questions, collects an answer per question (typed\n * to the question kind), resolves the caller's enrollment for the assessment's\n * course, and submits via `buiSubmitAssessment` ({ assessmentId, enrollmentId?,\n * answers }). Grading is done by an educator in the Grading queue — this page is\n * honest about that (\"submitted, awaiting grading\"). All data is live.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, CheckCircle2, ClipboardCheck, Send } from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n LoadingView,\n ErrorView,\n EmptyState,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiAssessment, useBuiEnrollments, useBuiSubmitAssessment } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport { formatDuration } from '../../utils/formatters';\nimport type { BuiQuestion } from '../../types';\n\ninterface Choice {\n label: string;\n}\n\n/** Narrow the untyped `options` JSON blob to a list of `{ label }` choices. */\nfunction parseChoices(options: unknown): Choice[] {\n if (!Array.isArray(options)) return [];\n return options\n .map((o) => (o && typeof o === 'object' && 'label' in o ? { label: String((o as { label: unknown }).label) } : null))\n .filter((c): c is Choice => c !== null && c.label.length > 0);\n}\n\ntype AnswerValue = string | string[];\n\nconst TRUE_FALSE_CHOICES: Choice[] = [{ label: 'True' }, { label: 'False' }];\n\nexport const TakeAssessmentPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { currentUser, navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const assessment = assessmentQuery.data ?? null;\n\n const enrollmentsQuery = useBuiEnrollments({\n learnerId: currentUser?.id,\n courseId: assessment?.courseId,\n enabled: Boolean(currentUser?.id && assessment?.courseId),\n });\n const enrollmentId = enrollmentsQuery.data?.items?.[0]?.id;\n\n const submit = useBuiSubmitAssessment();\n const [answers, setAnswers] = useState<Record<string, AnswerValue>>({});\n const [submittedId, setSubmittedId] = useState<string | null>(null);\n\n const questions = useMemo<BuiQuestion[]>(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const setAnswer = (qid: string, value: AnswerValue): void => {\n setAnswers((prev) => ({ ...prev, [qid]: value }));\n };\n\n const toggleMulti = (qid: string, label: string): void => {\n setAnswers((prev) => {\n const cur = Array.isArray(prev[qid]) ? (prev[qid] as string[]) : [];\n return { ...prev, [qid]: cur.includes(label) ? cur.filter((l) => l !== label) : [...cur, label] };\n });\n };\n\n const answeredCount = questions.filter((q) => {\n const a = answers[q.id];\n return Array.isArray(a) ? a.length > 0 : Boolean(a && String(a).trim());\n }).length;\n\n const handleSubmit = async (): Promise<void> => {\n if (!assessmentId) return;\n const result = await submit.mutateAsync({\n assessmentId,\n enrollmentId,\n attempt: 1,\n answers,\n });\n setSubmittedId(result.id);\n };\n\n const backToCourse = assessment\n ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" /> Back to course\n </Button>\n )\n : undefined;\n\n if (!assessmentId) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState illustration=\"empty-data\" title=\"No assessment selected\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading the assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError || !assessment) {\n return (\n <PageLayout title=\"Assessment\" actions={backToCourse}>\n <ErrorView\n title=\"Couldn't load this assessment\"\n message={assessmentQuery.error instanceof Error ? assessmentQuery.error.message : undefined}\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (assessment.status !== 'PUBLISHED') {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <EmptyState\n illustration=\"empty-data\"\n title=\"This assessment isn't open yet\"\n description=\"It has to be published by an educator before you can take it.\"\n />\n </PageLayout>\n );\n }\n\n // Submitted confirmation\n if (submittedId) {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <div className=\"mx-auto max-w-lg\">\n <GlassCard treatment=\"glass\" glow className=\"flex flex-col items-center gap-4 p-8 text-center\">\n <div className=\"flex h-14 w-14 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <CheckCircle2 className=\"h-7 w-7 text-status-success-text\" />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Submitted</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Your answers for “{assessment.title}” were submitted.\n {assessment.isGraded\n ? ' An educator will grade them — you’ll see your score and feedback in My Learning.'\n : ' Thanks for completing it.'}\n </p>\n </div>\n <div className=\"flex flex-wrap justify-center gap-2\">\n <Button variant=\"secondary\" onClick={() => navigateTo('learning')}>\n Go to My Learning\n </Button>\n <Button variant=\"ghost\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n Back to course\n </Button>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={assessment.title}\n description={assessment.description ?? undefined}\n actions={backToCourse}\n >\n <div className=\"mx-auto max-w-3xl\">\n {/* Meta strip */}\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <StatusBadge status={assessment.type} />\n <span className=\"text-sm text-text-secondary\">Passing score {assessment.passingScore}%</span>\n {assessment.timeLimitMin > 0 && (\n <span className=\"text-sm text-text-secondary\">· {formatDuration(assessment.timeLimitMin)}</span>\n )}\n <span className=\"text-sm text-text-secondary\">\n · {answeredCount}/{questions.length} answered\n </span>\n </div>\n\n {questions.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck className=\"h-8 w-8\" />}\n title=\"No questions yet\"\n description=\"This assessment doesn't have any questions to answer.\"\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {questions.map((q, idx) => {\n const choices =\n q.type === 'TRUE_FALSE' ? TRUE_FALSE_CHOICES : parseChoices(q.options);\n const answer = answers[q.id];\n return (\n <GlassCard key={q.id} treatment=\"plain\" className=\"p-5\">\n <div className=\"flex items-start gap-3\">\n <span className=\"mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary tabular-nums\">\n {idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary\">{q.prompt}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {q.points} {q.points === 1 ? 'point' : 'points'}\n </p>\n\n <div className=\"mt-4\">\n {(q.type === 'SINGLE_CHOICE' || q.type === 'TRUE_FALSE') && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => (\n <label\n key={c.label}\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors',\n answer === c.label\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"radio\"\n name={q.id}\n className=\"accent-[var(--color-accent-primary)]\"\n checked={answer === c.label}\n onChange={() => setAnswer(q.id, c.label)}\n />\n {c.label}\n </label>\n ))}\n </div>\n )}\n\n {q.type === 'MULTIPLE_CHOICE' && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => {\n const checked = Array.isArray(answer) && answer.includes(c.label);\n return (\n <label\n key={c.label}\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors',\n checked\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"checkbox\"\n className=\"accent-[var(--color-accent-primary)]\"\n checked={checked}\n onChange={() => toggleMulti(q.id, c.label)}\n />\n {c.label}\n </label>\n );\n })}\n </div>\n )}\n\n {q.type === 'SHORT_ANSWER' && (\n <Input\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder=\"Your answer\"\n />\n )}\n\n {(q.type === 'ESSAY' || q.type === 'CODE') && (\n <Textarea\n rows={q.type === 'CODE' ? 6 : 4}\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder={q.type === 'CODE' ? 'Write your code…' : 'Write your answer…'}\n className={q.type === 'CODE' ? 'font-mono text-sm' : undefined}\n />\n )}\n </div>\n </div>\n </div>\n </GlassCard>\n );\n })}\n\n {submit.isError && (\n <p className=\"text-sm text-status-error-text\">\n {submit.error instanceof Error ? submit.error.message : 'Could not submit your answers.'}\n </p>\n )}\n\n <div className=\"sticky bottom-0 flex items-center justify-between gap-3 border-t border-border-seam bg-bg-surface/90 py-3 backdrop-blur\">\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {answeredCount} of {questions.length} answered\n </span>\n <Button onClick={handleSubmit} disabled={submit.isPending || answeredCount === 0}>\n <Send className=\"mr-1 h-4 w-4\" />\n {submit.isPending ? 'Submitting…' : 'Submit assessment'}\n </Button>\n </div>\n </div>\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthoringPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/authoring/AuthoringPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAmLhC,eAAO,MAAM,aAAa,EAAE,EAgH3B,CAAC"}
|
|
@@ -216,15 +216,15 @@ var k = [
|
|
|
216
216
|
}), M.isLoading ? /* @__PURE__ */ C(a, { label: "Loading courses…" }) : M.isError ? /* @__PURE__ */ C(i, {
|
|
217
217
|
message: "We couldn't load your courses.",
|
|
218
218
|
onRetry: () => M.refetch()
|
|
219
|
-
}) : P.length === 0 ? /* @__PURE__ */ C(r, {
|
|
219
|
+
}) : /* @__PURE__ */ w(S, { children: [P.length === 0 ? /* @__PURE__ */ C(r, {
|
|
220
220
|
illustration: "empty-data",
|
|
221
221
|
title: I,
|
|
222
|
-
description: "Create your first course to start building curriculum, assessments and cohorts.",
|
|
222
|
+
description: g ? "None of the courses on this page are authored by you — clear \"Mine only\" or page through to find them." : "Create your first course to start building curriculum, assessments and cohorts.",
|
|
223
223
|
action: /* @__PURE__ */ w(T, {
|
|
224
224
|
onClick: () => E(!0),
|
|
225
225
|
children: [/* @__PURE__ */ C(x, { className: "mr-1.5 h-4 w-4" }), " New course"]
|
|
226
226
|
})
|
|
227
|
-
}) : /* @__PURE__ */
|
|
227
|
+
}) : /* @__PURE__ */ C("div", {
|
|
228
228
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
229
229
|
children: P.map((e) => /* @__PURE__ */ C(f, {
|
|
230
230
|
course: e,
|
|
@@ -234,7 +234,7 @@ var k = [
|
|
|
234
234
|
}), /* @__PURE__ */ C(s, {
|
|
235
235
|
page: O.page,
|
|
236
236
|
pageSize: A,
|
|
237
|
-
itemCount:
|
|
237
|
+
itemCount: P.length,
|
|
238
238
|
hasMore: M.data?.pageInfo.hasMore ?? !1,
|
|
239
239
|
onPageChange: (e) => O.onPageChange(e, M.data?.pageInfo.cursor)
|
|
240
240
|
})] })]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthoringPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/AuthoringPage.tsx"],"sourcesContent":["/**\n * AuthoringPage — the instructor course-management surface.\n * @module buildmyiq/pages/authoring\n *\n * Lists EVERY workspace course (including DRAFT / IN_REVIEW) via `useBuiCourses`,\n * filterable by workflow status and optionally narrowed to the caller's own\n * courses. A \"New course\" modal creates a draft (`useBuiCreateCourse`) and jumps\n * straight into the CourseBuilder. All data is live `bui*` data — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Plus } from 'lucide-react';\nimport { Button, Input, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n FilterTabs,\n Pagination,\n EmptyState,\n LoadingView,\n ErrorView,\n} from '../../components';\nimport { CourseCard } from '../_shared';\nimport { Modal, Field, SelectField, NO_SKILL, slugify } from './_forms';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiCourses, useBuiCreateCourse, useBuiSkills, useCursorPager } from '../../hooks';\nimport { COURSE_LEVELS } from '../../types';\nimport type { BuiCourse, BuiCourseLevel, BuiCourseStatus, BuiCreateCourseInput } from '../../types';\nimport { formatEnumLabel } from '../../utils/formatters';\n\ntype StatusFilter = 'ALL' | BuiCourseStatus;\n\nconst STATUS_TABS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IN_REVIEW', label: 'In review' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst PAGE_SIZE = 24;\n\n/**\n * Create-course modal. Mounted only while open so its form state resets each\n * time it is opened.\n */\nconst NewCourseModal: FC<{ onClose: () => void; onCreated: (course: BuiCourse) => void }> = ({\n onClose,\n onCreated,\n}) => {\n const { currentUser } = useBuildmyiq();\n const skillsQuery = useBuiSkills();\n const createCourse = useBuiCreateCourse();\n\n const [title, setTitle] = useState('');\n const [slug, setSlug] = useState('');\n const [slugEdited, setSlugEdited] = useState(false);\n const [summary, setSummary] = useState('');\n const [level, setLevel] = useState<BuiCourseLevel>('BEGINNER');\n const [language, setLanguage] = useState('en');\n const [estimatedHours, setEstimatedHours] = useState('');\n const [skillId, setSkillId] = useState<string>(NO_SKILL);\n const [error, setError] = useState<string | null>(null);\n\n const skills = skillsQuery.data ?? [];\n\n const onTitleChange = (value: string) => {\n setTitle(value);\n if (!slugEdited) setSlug(slugify(value));\n };\n\n const canSubmit = title.trim().length > 0 && slug.trim().length > 0 && !createCourse.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateCourseInput = {\n title: title.trim(),\n slug: slug.trim(),\n summary: summary.trim() || undefined,\n level,\n language: language.trim() || 'en',\n estimatedHours: estimatedHours.trim() ? Number(estimatedHours) : undefined,\n skillId: skillId !== NO_SKILL ? skillId : undefined,\n instructorId: currentUser?.id,\n };\n try {\n const course = await createCourse.mutateAsync(input);\n onCreated(course);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to create course.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"New course\"\n description=\"Create a draft course — you can add modules, lessons and assessments next.\"\n className=\"sm:max-w-xl\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createCourse.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createCourse.isPending ? 'Creating…' : 'Create course'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Title\" htmlFor=\"new-course-title\" required>\n <Input\n id=\"new-course-title\"\n value={title}\n onChange={(e) => onTitleChange(e.target.value)}\n placeholder=\"e.g. Foundations of Data Literacy\"\n />\n </Field>\n <Field\n label=\"Slug\"\n htmlFor=\"new-course-slug\"\n required\n hint=\"Used in the course URL. Auto-generated from the title — edit if you like.\"\n >\n <Input\n id=\"new-course-slug\"\n value={slug}\n onChange={(e) => {\n setSlug(e.target.value);\n setSlugEdited(true);\n }}\n placeholder=\"foundations-of-data-literacy\"\n />\n </Field>\n <Field label=\"Summary\" htmlFor=\"new-course-summary\">\n <Textarea\n id=\"new-course-summary\"\n rows={3}\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description shown on the catalog card.\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Level\">\n <SelectField\n value={level}\n onChange={(v) => setLevel(v as BuiCourseLevel)}\n options={COURSE_LEVELS.map((l) => ({ value: l, label: formatEnumLabel(l) }))}\n />\n </Field>\n <Field label=\"Language\" htmlFor=\"new-course-language\">\n <Input\n id=\"new-course-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"en\"\n />\n </Field>\n <Field label=\"Estimated hours\" htmlFor=\"new-course-hours\">\n <Input\n id=\"new-course-hours\"\n type=\"number\"\n min={0}\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n placeholder=\"0\"\n />\n </Field>\n <Field label=\"Skill\">\n <SelectField\n value={skillId}\n onChange={setSkillId}\n options={[\n { value: NO_SKILL, label: skillsQuery.isLoading ? 'Loading…' : 'No skill' },\n ...skills.map((s) => ({ value: s.id, label: s.name })),\n ]}\n />\n </Field>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nexport const AuthoringPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const myId = currentUser?.id;\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [mineOnly, setMineOnly] = useState(false);\n const [showCreate, setShowCreate] = useState(false);\n const pager = useCursorPager();\n\n const coursesQuery = useBuiCourses({\n status: status === 'ALL' ? undefined : status,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n\n const items: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const visible = useMemo(\n () => (mineOnly && myId ? items.filter((c) => c.instructorId === myId) : items),\n [items, mineOnly, myId]\n );\n\n const handleStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const emptyTitle = mineOnly\n ? 'No courses you author yet'\n : status === 'ALL'\n ? 'No courses yet'\n : `No ${formatEnumLabel(status).toLowerCase()} courses`;\n\n return (\n <PageLayout\n title=\"Authoring\"\n description=\"Create, curate and publish your workspace courses.\"\n titleGradient\n aurora\n actions={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n >\n <div className=\"flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <FilterTabs options={STATUS_TABS} value={status} onChange={handleStatus} />\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={mineOnly} onCheckedChange={setMineOnly} disabled={!currentUser?.id} />\n Mine only\n </label>\n </div>\n\n {coursesQuery.isLoading ? (\n <LoadingView label=\"Loading courses…\" />\n ) : coursesQuery.isError ? (\n <ErrorView\n message=\"We couldn't load your courses.\"\n onRetry={() => coursesQuery.refetch()}\n />\n ) : visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyTitle}\n description=\"Create your first course to start building curriculum, assessments and cohorts.\"\n action={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {visible.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n showStatus\n onClick={() => navigateTo(`authoring/${course.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={items.length}\n hasMore={coursesQuery.data?.pageInfo.hasMore ?? false}\n onPageChange={(p) => pager.onPageChange(p, coursesQuery.data?.pageInfo.cursor)}\n />\n </>\n )}\n </div>\n\n {showCreate && (\n <NewCourseModal\n onClose={() => setShowCreate(false)}\n onCreated={(course) => {\n setShowCreate(false);\n navigateTo(`authoring/${course.id}`);\n }}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuthoringPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/AuthoringPage.tsx"],"sourcesContent":["/**\n * AuthoringPage — the instructor course-management surface.\n * @module buildmyiq/pages/authoring\n *\n * Lists EVERY workspace course (including DRAFT / IN_REVIEW) via `useBuiCourses`,\n * filterable by workflow status and optionally narrowed to the caller's own\n * courses. A \"New course\" modal creates a draft (`useBuiCreateCourse`) and jumps\n * straight into the CourseBuilder. All data is live `bui*` data — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Plus } from 'lucide-react';\nimport { Button, Input, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n FilterTabs,\n Pagination,\n EmptyState,\n LoadingView,\n ErrorView,\n} from '../../components';\nimport { CourseCard } from '../_shared';\nimport { Modal, Field, SelectField, NO_SKILL, slugify } from './_forms';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiCourses, useBuiCreateCourse, useBuiSkills, useCursorPager } from '../../hooks';\nimport { COURSE_LEVELS } from '../../types';\nimport type { BuiCourse, BuiCourseLevel, BuiCourseStatus, BuiCreateCourseInput } from '../../types';\nimport { formatEnumLabel } from '../../utils/formatters';\n\ntype StatusFilter = 'ALL' | BuiCourseStatus;\n\nconst STATUS_TABS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IN_REVIEW', label: 'In review' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst PAGE_SIZE = 24;\n\n/**\n * Create-course modal. Mounted only while open so its form state resets each\n * time it is opened.\n */\nconst NewCourseModal: FC<{ onClose: () => void; onCreated: (course: BuiCourse) => void }> = ({\n onClose,\n onCreated,\n}) => {\n const { currentUser } = useBuildmyiq();\n const skillsQuery = useBuiSkills();\n const createCourse = useBuiCreateCourse();\n\n const [title, setTitle] = useState('');\n const [slug, setSlug] = useState('');\n const [slugEdited, setSlugEdited] = useState(false);\n const [summary, setSummary] = useState('');\n const [level, setLevel] = useState<BuiCourseLevel>('BEGINNER');\n const [language, setLanguage] = useState('en');\n const [estimatedHours, setEstimatedHours] = useState('');\n const [skillId, setSkillId] = useState<string>(NO_SKILL);\n const [error, setError] = useState<string | null>(null);\n\n const skills = skillsQuery.data ?? [];\n\n const onTitleChange = (value: string) => {\n setTitle(value);\n if (!slugEdited) setSlug(slugify(value));\n };\n\n const canSubmit = title.trim().length > 0 && slug.trim().length > 0 && !createCourse.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateCourseInput = {\n title: title.trim(),\n slug: slug.trim(),\n summary: summary.trim() || undefined,\n level,\n language: language.trim() || 'en',\n estimatedHours: estimatedHours.trim() ? Number(estimatedHours) : undefined,\n skillId: skillId !== NO_SKILL ? skillId : undefined,\n instructorId: currentUser?.id,\n };\n try {\n const course = await createCourse.mutateAsync(input);\n onCreated(course);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to create course.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"New course\"\n description=\"Create a draft course — you can add modules, lessons and assessments next.\"\n className=\"sm:max-w-xl\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createCourse.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createCourse.isPending ? 'Creating…' : 'Create course'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Title\" htmlFor=\"new-course-title\" required>\n <Input\n id=\"new-course-title\"\n value={title}\n onChange={(e) => onTitleChange(e.target.value)}\n placeholder=\"e.g. Foundations of Data Literacy\"\n />\n </Field>\n <Field\n label=\"Slug\"\n htmlFor=\"new-course-slug\"\n required\n hint=\"Used in the course URL. Auto-generated from the title — edit if you like.\"\n >\n <Input\n id=\"new-course-slug\"\n value={slug}\n onChange={(e) => {\n setSlug(e.target.value);\n setSlugEdited(true);\n }}\n placeholder=\"foundations-of-data-literacy\"\n />\n </Field>\n <Field label=\"Summary\" htmlFor=\"new-course-summary\">\n <Textarea\n id=\"new-course-summary\"\n rows={3}\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description shown on the catalog card.\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Level\">\n <SelectField\n value={level}\n onChange={(v) => setLevel(v as BuiCourseLevel)}\n options={COURSE_LEVELS.map((l) => ({ value: l, label: formatEnumLabel(l) }))}\n />\n </Field>\n <Field label=\"Language\" htmlFor=\"new-course-language\">\n <Input\n id=\"new-course-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"en\"\n />\n </Field>\n <Field label=\"Estimated hours\" htmlFor=\"new-course-hours\">\n <Input\n id=\"new-course-hours\"\n type=\"number\"\n min={0}\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n placeholder=\"0\"\n />\n </Field>\n <Field label=\"Skill\">\n <SelectField\n value={skillId}\n onChange={setSkillId}\n options={[\n { value: NO_SKILL, label: skillsQuery.isLoading ? 'Loading…' : 'No skill' },\n ...skills.map((s) => ({ value: s.id, label: s.name })),\n ]}\n />\n </Field>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nexport const AuthoringPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const myId = currentUser?.id;\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [mineOnly, setMineOnly] = useState(false);\n const [showCreate, setShowCreate] = useState(false);\n const pager = useCursorPager();\n\n const coursesQuery = useBuiCourses({\n status: status === 'ALL' ? undefined : status,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n\n const items: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const visible = useMemo(\n () => (mineOnly && myId ? items.filter((c) => c.instructorId === myId) : items),\n [items, mineOnly, myId]\n );\n\n const handleStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const emptyTitle = mineOnly\n ? 'No courses you author yet'\n : status === 'ALL'\n ? 'No courses yet'\n : `No ${formatEnumLabel(status).toLowerCase()} courses`;\n\n return (\n <PageLayout\n title=\"Authoring\"\n description=\"Create, curate and publish your workspace courses.\"\n titleGradient\n aurora\n actions={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n >\n <div className=\"flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <FilterTabs options={STATUS_TABS} value={status} onChange={handleStatus} />\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={mineOnly} onCheckedChange={setMineOnly} disabled={!currentUser?.id} />\n Mine only\n </label>\n </div>\n\n {coursesQuery.isLoading ? (\n <LoadingView label=\"Loading courses…\" />\n ) : coursesQuery.isError ? (\n <ErrorView\n message=\"We couldn't load your courses.\"\n onRetry={() => coursesQuery.refetch()}\n />\n ) : (\n <>\n {visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyTitle}\n description={\n mineOnly\n ? 'None of the courses on this page are authored by you — clear \"Mine only\" or page through to find them.'\n : 'Create your first course to start building curriculum, assessments and cohorts.'\n }\n action={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {visible.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n showStatus\n onClick={() => navigateTo(`authoring/${course.id}`)}\n />\n ))}\n </div>\n )}\n {/* Pager stays visible even when mineOnly trims the page empty, so the\n user can advance to later pages to find their courses. */}\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={visible.length}\n hasMore={coursesQuery.data?.pageInfo.hasMore ?? false}\n onPageChange={(p) => pager.onPageChange(p, coursesQuery.data?.pageInfo.cursor)}\n />\n </>\n )}\n </div>\n\n {showCreate && (\n <NewCourseModal\n onClose={() => setShowCreate(false)}\n onCreated={(course) => {\n setShowCreate(false);\n navigateTo(`authoring/${course.id}`);\n }}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CourseBuilderPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/authoring/CourseBuilderPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAsqBhC,eAAO,MAAM,iBAAiB,EAAE,EA2E/B,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FC, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/** Sentinel value for an "unset" optional Select (radix forbids an empty string value). */
|
|
4
|
+
export declare const NO_SKILL = "__none__";
|
|
5
|
+
/** Turn a free-text title into a URL-safe slug. */
|
|
6
|
+
export declare const slugify: (text: string) => string;
|
|
7
|
+
interface ModalProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
footer?: ReactNode;
|
|
13
|
+
className?: string;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/** Controlled modal built on the fe-libs ResponsiveDialog. Closes on ESC / overlay / X. */
|
|
17
|
+
export declare const Modal: FC<ModalProps>;
|
|
18
|
+
interface FieldProps {
|
|
19
|
+
label: string;
|
|
20
|
+
htmlFor?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
hint?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
/** Label + control + hint/error wrapper for form rows. */
|
|
28
|
+
export declare const Field: FC<FieldProps>;
|
|
29
|
+
export interface SelectOption {
|
|
30
|
+
value: string;
|
|
31
|
+
label: string;
|
|
32
|
+
}
|
|
33
|
+
interface SelectFieldProps {
|
|
34
|
+
id?: string;
|
|
35
|
+
value: string;
|
|
36
|
+
onChange: (value: string) => void;
|
|
37
|
+
options: SelectOption[];
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** A single-select control over the fe-libs Select primitive. */
|
|
42
|
+
export declare const SelectField: FC<SelectFieldProps>;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=_forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_forms.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/authoring/_forms.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAiB3C,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,aAAa,CAAC;AAEnC,mDAAmD;AACnD,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,MAMtB,CAAC;AAElB,UAAU,UAAU;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,2FAA2F;AAC3F,eAAO,MAAM,KAAK,EAAE,EAAE,CAAC,UAAU,CAwBhC,CAAC;AAEF,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,0DAA0D;AAC1D,eAAO,MAAM,KAAK,EAAE,EAAE,CAAC,UAAU,CAkBhC,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,gBAAgB;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iEAAiE;AACjE,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAoB5C,CAAC"}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
2
|
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { Label as r, ResponsiveDialog as i, ResponsiveDialogContent as a, ResponsiveDialogDescription as o, ResponsiveDialogFooter as s, ResponsiveDialogHeader as c, ResponsiveDialogTitle as l, Select as u, SelectContent as d, SelectItem as f, SelectTrigger as p, SelectValue as m } from "@burdenoff/fe-libs/ui";
|
|
4
4
|
//#region src/buildmyiq/pages/authoring/_forms.tsx
|
|
5
|
-
var h = "__none__", g = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80), _ = ({ open:
|
|
6
|
-
open:
|
|
5
|
+
var h = "__none__", g = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80), _ = ({ open: r, onClose: u, title: d, description: f, footer: p, className: m, children: h }) => /* @__PURE__ */ t(i, {
|
|
6
|
+
open: r,
|
|
7
7
|
onOpenChange: (e) => {
|
|
8
8
|
e || u();
|
|
9
9
|
},
|
|
10
|
-
children: /* @__PURE__ */ n(
|
|
10
|
+
children: /* @__PURE__ */ n(a, {
|
|
11
11
|
className: e("max-h-[90vh] overflow-y-auto", m),
|
|
12
12
|
children: [
|
|
13
|
-
/* @__PURE__ */ n(
|
|
13
|
+
/* @__PURE__ */ n(c, { children: [/* @__PURE__ */ t(l, { children: d }), f && /* @__PURE__ */ t(o, { children: f })] }),
|
|
14
14
|
h,
|
|
15
|
-
p && /* @__PURE__ */ t(
|
|
15
|
+
p && /* @__PURE__ */ t(s, { children: p })
|
|
16
16
|
]
|
|
17
17
|
})
|
|
18
|
-
}), v = ({ label:
|
|
19
|
-
className: e("grid gap-1.5",
|
|
18
|
+
}), v = ({ label: i, htmlFor: a, required: o, hint: s, error: c, className: l, children: u }) => /* @__PURE__ */ n("div", {
|
|
19
|
+
className: e("grid gap-1.5", l),
|
|
20
20
|
children: [
|
|
21
|
-
/* @__PURE__ */ n(
|
|
22
|
-
htmlFor:
|
|
23
|
-
children: [
|
|
21
|
+
/* @__PURE__ */ n(r, {
|
|
22
|
+
htmlFor: a,
|
|
23
|
+
children: [i, o && /* @__PURE__ */ t("span", {
|
|
24
24
|
className: "text-status-error-text",
|
|
25
25
|
children: " *"
|
|
26
26
|
})]
|
|
27
27
|
}),
|
|
28
28
|
u,
|
|
29
|
-
|
|
29
|
+
s && !c && /* @__PURE__ */ t("p", {
|
|
30
30
|
className: "text-xs text-text-tertiary",
|
|
31
|
-
children:
|
|
31
|
+
children: s
|
|
32
32
|
}),
|
|
33
|
-
|
|
33
|
+
c && /* @__PURE__ */ t("p", {
|
|
34
34
|
className: "text-xs text-status-error-text",
|
|
35
|
-
children:
|
|
35
|
+
children: c
|
|
36
36
|
})
|
|
37
37
|
]
|
|
38
38
|
}), y = ({ id: e, value: r, onChange: i, options: a, placeholder: o, disabled: s }) => /* @__PURE__ */ n(u, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_forms.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/_forms.tsx"],"sourcesContent":["/**\n * Authoring form primitives — co-located helpers shared by AuthoringPage and\n * CourseBuilderPage only.\n * @module buildmyiq/pages/authoring\n *\n * Thin, token-only wrappers over the fe-libs
|
|
1
|
+
{"version":3,"file":"_forms.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/_forms.tsx"],"sourcesContent":["/**\n * Authoring form primitives — co-located helpers shared by AuthoringPage and\n * CourseBuilderPage only.\n * @module buildmyiq/pages/authoring\n *\n * Thin, token-only wrappers over the fe-libs ResponsiveDialog + Select primitives so the\n * authoring create/edit modals stay consistent and free of duplication. These\n * are intentionally NOT exported from the components barrel — they are private\n * to the authoring section.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../../utils/cn';\n\n/** Sentinel value for an \"unset\" optional Select (radix forbids an empty string value). */\nexport const NO_SKILL = '__none__';\n\n/** Turn a free-text title into a URL-safe slug. */\nexport const slugify = (text: string): string =>\n text\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n\ninterface ModalProps {\n open: boolean;\n onClose: () => void;\n title: string;\n description?: string;\n footer?: ReactNode;\n className?: string;\n children: ReactNode;\n}\n\n/** Controlled modal built on the fe-libs ResponsiveDialog. Closes on ESC / overlay / X. */\nexport const Modal: FC<ModalProps> = ({\n open,\n onClose,\n title,\n description,\n footer,\n className,\n children,\n}) => (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n if (!next) onClose();\n }}\n >\n <ResponsiveDialogContent className={cn('max-h-[90vh] overflow-y-auto', className)}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{title}</ResponsiveDialogTitle>\n {description && <ResponsiveDialogDescription>{description}</ResponsiveDialogDescription>}\n </ResponsiveDialogHeader>\n {children}\n {footer && <ResponsiveDialogFooter>{footer}</ResponsiveDialogFooter>}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n);\n\ninterface FieldProps {\n label: string;\n htmlFor?: string;\n required?: boolean;\n hint?: string;\n error?: string;\n className?: string;\n children: ReactNode;\n}\n\n/** Label + control + hint/error wrapper for form rows. */\nexport const Field: FC<FieldProps> = ({\n label,\n htmlFor,\n required,\n hint,\n error,\n className,\n children,\n}) => (\n <div className={cn('grid gap-1.5', className)}>\n <Label htmlFor={htmlFor}>\n {label}\n {required && <span className=\"text-status-error-text\"> *</span>}\n </Label>\n {children}\n {hint && !error && <p className=\"text-xs text-text-tertiary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n);\n\nexport interface SelectOption {\n value: string;\n label: string;\n}\n\ninterface SelectFieldProps {\n id?: string;\n value: string;\n onChange: (value: string) => void;\n options: SelectOption[];\n placeholder?: string;\n disabled?: boolean;\n}\n\n/** A single-select control over the fe-libs Select primitive. */\nexport const SelectField: FC<SelectFieldProps> = ({\n id,\n value,\n onChange,\n options,\n placeholder,\n disabled,\n}) => (\n <Select value={value} onValueChange={onChange} disabled={disabled}>\n <SelectTrigger id={id} className=\"w-full\">\n <SelectValue placeholder={placeholder} />\n </SelectTrigger>\n <SelectContent>\n {options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n);\n"],"mappings":";;;;AA6BA,IAAa,IAAW,YAGX,KAAW,MACtB,EACG,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,CACvB,MAAM,GAAG,GAAG,EAaJ,KAAyB,EACpC,SACA,YACA,UACA,gBACA,WACA,cACA,kBAEA,kBAAC,GAAD;CACQ;CACN,eAAe,MAAS;AACtB,EAAK,KAAM,GAAS;;WAGtB,kBAAC,GAAD;EAAyB,WAAW,EAAG,gCAAgC,EAAU;YAAjF;GACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAwB,GAA8B,CAAA,EACrD,KAAe,kBAAC,GAAD,EAAA,UAA8B,GAA0C,CAAA,CACjE,EAAA,CAAA;GACxB;GACA,KAAU,kBAAC,GAAD,EAAA,UAAyB,GAAgC,CAAA;GAC5C;;CACT,CAAA,EAcR,KAAyB,EACpC,UACA,YACA,aACA,SACA,UACA,cACA,kBAEA,kBAAC,OAAD;CAAK,WAAW,EAAG,gBAAgB,EAAU;WAA7C;EACE,kBAAC,GAAD;GAAgB;aAAhB,CACG,GACA,KAAY,kBAAC,QAAD;IAAM,WAAU;cAAyB;IAAS,CAAA,CACzD;;EACP;EACA,KAAQ,CAAC,KAAS,kBAAC,KAAD;GAAG,WAAU;aAA8B;GAAS,CAAA;EACtE,KAAS,kBAAC,KAAD;GAAG,WAAU;aAAkC;GAAU,CAAA;EAC/D;IAkBK,KAAqC,EAChD,OACA,UACA,aACA,YACA,gBACA,kBAEA,kBAAC,GAAD;CAAe;CAAO,eAAe;CAAoB;WAAzD,CACE,kBAAC,GAAD;EAAmB;EAAI,WAAU;YAC/B,kBAAC,GAAD,EAA0B,gBAAe,CAAA;EAC3B,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,GAAD;EAA4B,OAAO,EAAI;YACpC,EAAI;EACM,EAFI,EAAI,MAER,CACb,EACY,CAAA,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CatalogPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/catalog/CatalogPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAwBhC,eAAO,MAAM,WAAW,EAAE,EAkHzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CourseDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/catalog/CourseDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAgFhC,eAAO,MAAM,gBAAgB,EAAE,EA+V9B,CAAC"}
|
|
@@ -312,7 +312,15 @@ var B = "buildmyiq-course", V = {
|
|
|
312
312
|
}),
|
|
313
313
|
/* @__PURE__ */ L("div", {
|
|
314
314
|
className: "flex shrink-0 items-center gap-2",
|
|
315
|
-
children: [
|
|
315
|
+
children: [
|
|
316
|
+
/* @__PURE__ */ I(a, { status: e.type }),
|
|
317
|
+
/* @__PURE__ */ I(a, { status: e.status }),
|
|
318
|
+
e.status === "PUBLISHED" && X && /* @__PURE__ */ I(R, {
|
|
319
|
+
size: "sm",
|
|
320
|
+
onClick: () => T(`assessments/${e.id}/take`),
|
|
321
|
+
children: "Take"
|
|
322
|
+
})
|
|
323
|
+
]
|
|
316
324
|
})
|
|
317
325
|
]
|
|
318
326
|
}, e.id))
|