@burdenoff/microfe-buildmyiq 2026.705.9 → 2026.705.11

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.
Files changed (29) hide show
  1. package/dist/buildmyiq/BuildmyiqRoot.js +54 -46
  2. package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
  3. package/dist/buildmyiq/BuildmyiqRoutes.js +19 -11
  4. package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
  5. package/dist/buildmyiq/components/DataTable.js +1 -1
  6. package/dist/buildmyiq/components/DataTable.js.map +1 -1
  7. package/dist/buildmyiq/components/MiniBar.js +1 -1
  8. package/dist/buildmyiq/components/MiniBar.js.map +1 -1
  9. package/dist/buildmyiq/hooks/useBuildmyiqApi.js +23 -15
  10. package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
  11. package/dist/buildmyiq/pages/DashboardPage.js +2 -2
  12. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  13. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +264 -0
  14. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -0
  15. package/dist/buildmyiq/pages/authoring/AuthoringPage.js +4 -4
  16. package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
  17. package/dist/buildmyiq/pages/authoring/_forms.js +15 -15
  18. package/dist/buildmyiq/pages/authoring/_forms.js.map +1 -1
  19. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +9 -1
  20. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  21. package/dist/buildmyiq/pages/certificates/CertificatesPage.js +1 -1
  22. package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
  23. package/dist/buildmyiq/pages/cohorts/CohortsPage.js +1 -1
  24. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
  25. package/dist/buildmyiq/pages/insights/InsightsPage.js +242 -0
  26. package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -0
  27. package/dist/buildmyiq/pages/learning/MyLearningPage.js +83 -77
  28. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  29. package/package.json +1 -1
@@ -0,0 +1,242 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { PageLayout as t } from "../../components/PageLayout.js";
3
+ import { MetricsCard as n } from "../../components/MetricsCard.js";
4
+ import { formatEnumLabel as r, formatPercent as i } from "../../utils/formatters.js";
5
+ import { EmptyState as a } from "../../components/EmptyState.js";
6
+ import { LoadingView as o, Section as s } from "../../components/StateViews.js";
7
+ import { MiniBar as c } from "../../components/MiniBar.js";
8
+ import { useBuiCertificates as l, useBuiCourses as u, useBuiEnrollments as d, useBuiSubmissions as f } from "../../hooks/useBuildmyiqApi.js";
9
+ import "../../hooks/index.js";
10
+ import "../../components/index.js";
11
+ import { useMemo as p } from "react";
12
+ import { Award as m, BookOpen as h, CheckCircle2 as g, GraduationCap as _, TrendingUp as v, Users as y } from "lucide-react";
13
+ import { jsx as b, jsxs as x } from "react/jsx-runtime";
14
+ import { GlassCard as S } from "@burdenoff/fe-libs/ui";
15
+ //#region src/buildmyiq/pages/insights/InsightsPage.tsx
16
+ var C = [
17
+ "ENROLLED",
18
+ "IN_PROGRESS",
19
+ "COMPLETED",
20
+ "DROPPED",
21
+ "EXPIRED"
22
+ ], w = ({ rows: e }) => /* @__PURE__ */ b("div", {
23
+ className: "flex flex-col gap-3",
24
+ children: e.map((e) => /* @__PURE__ */ x("div", {
25
+ className: "flex items-center gap-3",
26
+ children: [
27
+ /* @__PURE__ */ b("span", {
28
+ className: "w-28 shrink-0 text-sm text-text-secondary",
29
+ children: e.label
30
+ }),
31
+ /* @__PURE__ */ b(c, {
32
+ value: e.pct,
33
+ barClassName: e.tone,
34
+ className: "flex-1",
35
+ height: 8
36
+ }),
37
+ /* @__PURE__ */ b("span", {
38
+ className: "w-10 shrink-0 text-right text-sm tabular-nums text-text-primary",
39
+ children: e.count
40
+ })
41
+ ]
42
+ }, e.label))
43
+ }), T = () => {
44
+ let T = u({ limit: 100 }), E = d({ limit: 100 }), D = f({ limit: 100 }), O = l(), k = p(() => T.data?.items ?? [], [T.data]), A = p(() => E.data?.items ?? [], [E.data]), j = p(() => D.data?.items ?? [], [D.data]), M = O.data ?? [], N = p(() => {
45
+ let e = k.filter((e) => e.status === "PUBLISHED").length, t = A.filter((e) => e.status === "COMPLETED").length, n = A.length > 0 ? t / A.length * 100 : 0, r = A.length > 0 ? A.reduce((e, t) => e + (t.progressPct ?? 0), 0) / A.length : 0, i = j.filter((e) => e.status === "GRADED"), a = i.filter((e) => e.passed === !0).length, o = i.length > 0 ? a / i.length * 100 : 0, s = new Set(A.map((e) => e.learnerId)).size;
46
+ return {
47
+ published: e,
48
+ completed: t,
49
+ completionRate: n,
50
+ avgProgress: r,
51
+ gradedCount: i.length,
52
+ passRate: o,
53
+ activeLearners: s
54
+ };
55
+ }, [
56
+ k,
57
+ A,
58
+ j
59
+ ]), P = p(() => {
60
+ let e = A.length, t = {
61
+ ENROLLED: "bg-[var(--color-accent-teal)]",
62
+ IN_PROGRESS: "bg-[var(--color-accent-teal)]",
63
+ COMPLETED: "bg-[var(--color-status-success-text)]",
64
+ DROPPED: "bg-[var(--color-status-error-text)]",
65
+ EXPIRED: "bg-[var(--color-text-tertiary)]"
66
+ };
67
+ return C.map((n) => {
68
+ let i = A.filter((e) => e.status === n).length;
69
+ return {
70
+ label: r(n),
71
+ count: i,
72
+ pct: e > 0 ? i / e * 100 : 0,
73
+ tone: t[n] ?? "bg-accent"
74
+ };
75
+ }).filter((e) => e.count > 0);
76
+ }, [A]), F = p(() => {
77
+ let e = new Map(k.map((e) => [e.id, e.title])), t = /* @__PURE__ */ new Map();
78
+ for (let e of A) t.set(e.courseId, (t.get(e.courseId) ?? 0) + 1);
79
+ let n = Math.max(1, ...t.values());
80
+ return [...t.entries()].map(([t, r]) => ({
81
+ courseId: t,
82
+ title: e.get(t) ?? "Course",
83
+ count: r,
84
+ pct: r / n * 100
85
+ })).sort((e, t) => t.count - e.count).slice(0, 6);
86
+ }, [k, A]), I = T.isLoading || E.isLoading || D.isLoading, L = k.length > 0 || A.length > 0 || j.length > 0 || M.length > 0;
87
+ return /* @__PURE__ */ b(t, {
88
+ title: "Insights",
89
+ description: "Learning analytics across your workspace — completion, engagement and outcomes.",
90
+ titleGradient: !0,
91
+ aurora: !0,
92
+ children: I ? /* @__PURE__ */ b(o, { label: "Crunching your learning data…" }) : L ? /* @__PURE__ */ x("div", {
93
+ className: "flex flex-col gap-8",
94
+ children: [
95
+ /* @__PURE__ */ x("div", {
96
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4",
97
+ children: [
98
+ /* @__PURE__ */ b(n, {
99
+ icon: /* @__PURE__ */ b(h, { className: "h-5 w-5" }),
100
+ value: N.published,
101
+ label: "Published courses",
102
+ featured: !0
103
+ }),
104
+ /* @__PURE__ */ b(n, {
105
+ icon: /* @__PURE__ */ b(y, { className: "h-5 w-5" }),
106
+ value: N.activeLearners,
107
+ label: "Active learners"
108
+ }),
109
+ /* @__PURE__ */ b(n, {
110
+ icon: /* @__PURE__ */ b(v, { className: "h-5 w-5" }),
111
+ value: i(N.completionRate),
112
+ label: "Completion rate",
113
+ accent: "success",
114
+ footer: /* @__PURE__ */ b(c, {
115
+ value: N.completionRate,
116
+ barClassName: "bg-[var(--color-status-success-text)]"
117
+ })
118
+ }),
119
+ /* @__PURE__ */ b(n, {
120
+ icon: /* @__PURE__ */ b(m, { className: "h-5 w-5" }),
121
+ value: M.length,
122
+ label: "Certificates issued"
123
+ })
124
+ ]
125
+ }),
126
+ /* @__PURE__ */ x("div", {
127
+ className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
128
+ children: [/* @__PURE__ */ b(s, {
129
+ title: "Enrolment status",
130
+ description: `${A.length} enrolments`,
131
+ children: P.length === 0 ? /* @__PURE__ */ b("p", {
132
+ className: "text-sm text-text-secondary",
133
+ children: "No enrolments yet."
134
+ }) : /* @__PURE__ */ x(S, {
135
+ treatment: "plain",
136
+ className: "p-5",
137
+ children: [/* @__PURE__ */ b(w, { rows: P }), /* @__PURE__ */ x("div", {
138
+ className: "mt-4 flex items-center gap-2 border-t border-border-seam pt-3",
139
+ children: [/* @__PURE__ */ b(_, { className: "h-4 w-4 text-text-secondary" }), /* @__PURE__ */ x("span", {
140
+ className: "text-sm text-text-secondary",
141
+ children: [
142
+ "Average progress",
143
+ " ",
144
+ /* @__PURE__ */ b("span", {
145
+ className: "font-medium text-text-primary",
146
+ children: i(N.avgProgress)
147
+ })
148
+ ]
149
+ })]
150
+ })]
151
+ })
152
+ }), /* @__PURE__ */ b(s, {
153
+ title: "Assessment outcomes",
154
+ description: `${j.length} submissions`,
155
+ children: /* @__PURE__ */ x(S, {
156
+ treatment: "plain",
157
+ className: "flex flex-col gap-4 p-5",
158
+ children: [
159
+ /* @__PURE__ */ x("div", {
160
+ className: "flex items-center justify-between",
161
+ children: [/* @__PURE__ */ b("span", {
162
+ className: "text-sm text-text-secondary",
163
+ children: "Graded"
164
+ }), /* @__PURE__ */ x("span", {
165
+ className: "text-sm font-medium tabular-nums text-text-primary",
166
+ children: [
167
+ N.gradedCount,
168
+ "/",
169
+ j.length
170
+ ]
171
+ })]
172
+ }),
173
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ x("div", {
174
+ className: "mb-1 flex items-center justify-between",
175
+ children: [/* @__PURE__ */ x("span", {
176
+ className: "inline-flex items-center gap-1.5 text-sm text-text-secondary",
177
+ children: [/* @__PURE__ */ b(g, { className: "h-4 w-4 text-status-success-text" }), " Pass rate"]
178
+ }), /* @__PURE__ */ b("span", {
179
+ className: "text-sm font-medium tabular-nums text-text-primary",
180
+ children: i(N.passRate)
181
+ })]
182
+ }), /* @__PURE__ */ b(c, {
183
+ value: N.passRate,
184
+ barClassName: "bg-[var(--color-status-success-text)]",
185
+ height: 8
186
+ })] }),
187
+ N.gradedCount === 0 && /* @__PURE__ */ b("p", {
188
+ className: "text-xs text-text-secondary",
189
+ children: "No submissions have been graded yet — pass rate appears once grading begins."
190
+ })
191
+ ]
192
+ })
193
+ })]
194
+ }),
195
+ /* @__PURE__ */ b(s, {
196
+ title: "Top courses by enrolment",
197
+ children: F.length === 0 ? /* @__PURE__ */ b("p", {
198
+ className: "text-sm text-text-secondary",
199
+ children: "No enrolments yet."
200
+ }) : /* @__PURE__ */ b(S, {
201
+ treatment: "plain",
202
+ className: "p-5",
203
+ children: /* @__PURE__ */ b("div", {
204
+ className: "flex flex-col gap-3",
205
+ children: F.map((t, n) => /* @__PURE__ */ x("div", {
206
+ className: "flex items-center gap-3",
207
+ children: [
208
+ /* @__PURE__ */ b("span", {
209
+ className: e("flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold tabular-nums", n === 0 ? "bg-[var(--color-accent-soft)] text-text-primary" : "bg-bg-sunken text-text-secondary"),
210
+ children: n + 1
211
+ }),
212
+ /* @__PURE__ */ b("span", {
213
+ className: "w-48 shrink-0 truncate text-sm text-text-primary",
214
+ children: t.title
215
+ }),
216
+ /* @__PURE__ */ b(c, {
217
+ value: t.pct,
218
+ barClassName: "bg-[var(--color-accent-teal)]",
219
+ className: "flex-1",
220
+ height: 8
221
+ }),
222
+ /* @__PURE__ */ b("span", {
223
+ className: "w-10 shrink-0 text-right text-sm tabular-nums text-text-secondary",
224
+ children: t.count
225
+ })
226
+ ]
227
+ }, t.courseId))
228
+ })
229
+ })
230
+ })
231
+ ]
232
+ }) : /* @__PURE__ */ b(a, {
233
+ illustration: "empty-data",
234
+ title: "No data to analyse yet",
235
+ description: "Publish courses and enrol learners — insights appear here as activity accrues."
236
+ })
237
+ });
238
+ };
239
+ //#endregion
240
+ export { T as InsightsPage };
241
+
242
+ //# sourceMappingURL=InsightsPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/insights/InsightsPage.tsx"],"sourcesContent":["/**\n * InsightsPage — educator/admin learning analytics, DERIVED entirely from live\n * `bui*` data (no analytics tables, no fabricated numbers).\n * @module buildmyiq/pages/insights\n *\n * Everything here is computed client-side from real queries:\n * - courses (total / published / by level)\n * - enrollments (status split, completion rate, average progress)\n * - submissions (graded / pass rate)\n * - certificates issued\n * - top courses by enrolment\n * When the workspace has no data yet, the page shows honest zero/empty states.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, GraduationCap, CheckCircle2, Award, TrendingUp, Users } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, MetricsCard, LoadingView, Section, EmptyState, MiniBar } from '../../components';\nimport {\n useBuiCourses,\n useBuiEnrollments,\n useBuiSubmissions,\n useBuiCertificates,\n} from '../../hooks';\nimport { formatEnumLabel, formatPercent } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { BuiCourse, BuiEnrollment, BuiSubmission } from '../../types';\n\nconst ENROLLMENT_STATUS_ORDER = ['ENROLLED', 'IN_PROGRESS', 'COMPLETED', 'DROPPED', 'EXPIRED'] as const;\n\ninterface DistRow {\n label: string;\n count: number;\n pct: number;\n tone: string;\n}\n\nconst DistBars: FC<{ rows: DistRow[] }> = ({ rows }) => (\n <div className=\"flex flex-col gap-3\">\n {rows.map((r) => (\n <div key={r.label} className=\"flex items-center gap-3\">\n <span className=\"w-28 shrink-0 text-sm text-text-secondary\">{r.label}</span>\n <MiniBar value={r.pct} barClassName={r.tone} className=\"flex-1\" height={8} />\n <span className=\"w-10 shrink-0 text-right text-sm tabular-nums text-text-primary\">{r.count}</span>\n </div>\n ))}\n </div>\n);\n\nexport const InsightsPage: FC = () => {\n const coursesQuery = useBuiCourses({ limit: 100 });\n const enrollmentsQuery = useBuiEnrollments({ limit: 100 });\n const submissionsQuery = useBuiSubmissions({ limit: 100 });\n const certsQuery = useBuiCertificates();\n\n const courses: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const enrollments: BuiEnrollment[] = useMemo(\n () => enrollmentsQuery.data?.items ?? [],\n [enrollmentsQuery.data]\n );\n const submissions: BuiSubmission[] = useMemo(\n () => submissionsQuery.data?.items ?? [],\n [submissionsQuery.data]\n );\n const certificates = certsQuery.data ?? [];\n\n const stats = useMemo(() => {\n const published = courses.filter((c) => c.status === 'PUBLISHED').length;\n const completed = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const completionRate = enrollments.length > 0 ? (completed / enrollments.length) * 100 : 0;\n const avgProgress =\n enrollments.length > 0\n ? enrollments.reduce((s, e) => s + (e.progressPct ?? 0), 0) / enrollments.length\n : 0;\n const graded = submissions.filter((s) => s.status === 'GRADED');\n const passed = graded.filter((s) => s.passed === true).length;\n const passRate = graded.length > 0 ? (passed / graded.length) * 100 : 0;\n const activeLearners = new Set(enrollments.map((e) => e.learnerId)).size;\n return {\n published,\n completed,\n completionRate,\n avgProgress,\n gradedCount: graded.length,\n passRate,\n activeLearners,\n };\n }, [courses, enrollments, submissions]);\n\n const enrollmentDist: DistRow[] = useMemo(() => {\n const total = enrollments.length;\n const tones: Record<string, string> = {\n ENROLLED: 'bg-[var(--color-accent-teal)]',\n IN_PROGRESS: 'bg-[var(--color-accent-teal)]',\n COMPLETED: 'bg-[var(--color-status-success-text)]',\n DROPPED: 'bg-[var(--color-status-error-text)]',\n EXPIRED: 'bg-[var(--color-text-tertiary)]',\n };\n return ENROLLMENT_STATUS_ORDER.map((status) => {\n const count = enrollments.filter((e) => e.status === status).length;\n return {\n label: formatEnumLabel(status),\n count,\n pct: total > 0 ? (count / total) * 100 : 0,\n tone: tones[status] ?? 'bg-accent',\n };\n }).filter((r) => r.count > 0);\n }, [enrollments]);\n\n const topCourses = useMemo(() => {\n const titleById = new Map(courses.map((c) => [c.id, c.title]));\n const counts = new Map<string, number>();\n for (const e of enrollments) counts.set(e.courseId, (counts.get(e.courseId) ?? 0) + 1);\n const max = Math.max(1, ...counts.values());\n return [...counts.entries()]\n .map(([courseId, count]) => ({\n courseId,\n title: titleById.get(courseId) ?? 'Course',\n count,\n pct: (count / max) * 100,\n }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 6);\n }, [courses, enrollments]);\n\n const isLoading =\n coursesQuery.isLoading || enrollmentsQuery.isLoading || submissionsQuery.isLoading;\n\n const hasAnyData =\n courses.length > 0 || enrollments.length > 0 || submissions.length > 0 || certificates.length > 0;\n\n return (\n <PageLayout\n title=\"Insights\"\n description=\"Learning analytics across your workspace — completion, engagement and outcomes.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Crunching your learning data…\" />\n ) : !hasAnyData ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No data to analyse yet\"\n description=\"Publish courses and enrol learners — insights appear here as activity accrues.\"\n />\n ) : (\n <div className=\"flex flex-col gap-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <MetricsCard\n icon={<BookOpen className=\"h-5 w-5\" />}\n value={stats.published}\n label=\"Published courses\"\n featured\n />\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={stats.activeLearners}\n label=\"Active learners\"\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatPercent(stats.completionRate)}\n label=\"Completion rate\"\n accent=\"success\"\n footer={<MiniBar value={stats.completionRate} barClassName=\"bg-[var(--color-status-success-text)]\" />}\n />\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={certificates.length}\n label=\"Certificates issued\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Enrollment status distribution */}\n <Section title=\"Enrolment status\" description={`${enrollments.length} enrolments`}>\n {enrollmentDist.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No enrolments yet.</p>\n ) : (\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <DistBars rows={enrollmentDist} />\n <div className=\"mt-4 flex items-center gap-2 border-t border-border-seam pt-3\">\n <GraduationCap className=\"h-4 w-4 text-text-secondary\" />\n <span className=\"text-sm text-text-secondary\">\n Average progress{' '}\n <span className=\"font-medium text-text-primary\">\n {formatPercent(stats.avgProgress)}\n </span>\n </span>\n </div>\n </GlassCard>\n )}\n </Section>\n\n {/* Assessment outcomes */}\n <Section title=\"Assessment outcomes\" description={`${submissions.length} submissions`}>\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">Graded</span>\n <span className=\"text-sm font-medium tabular-nums text-text-primary\">\n {stats.gradedCount}/{submissions.length}\n </span>\n </div>\n <div>\n <div className=\"mb-1 flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text\" /> Pass rate\n </span>\n <span className=\"text-sm font-medium tabular-nums text-text-primary\">\n {formatPercent(stats.passRate)}\n </span>\n </div>\n <MiniBar\n value={stats.passRate}\n barClassName=\"bg-[var(--color-status-success-text)]\"\n height={8}\n />\n </div>\n {stats.gradedCount === 0 && (\n <p className=\"text-xs text-text-secondary\">\n No submissions have been graded yet — pass rate appears once grading begins.\n </p>\n )}\n </GlassCard>\n </Section>\n </div>\n\n {/* Top courses by enrolment */}\n <Section title=\"Top courses by enrolment\">\n {topCourses.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No enrolments yet.</p>\n ) : (\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <div className=\"flex flex-col gap-3\">\n {topCourses.map((c, i) => (\n <div key={c.courseId} className=\"flex items-center gap-3\">\n <span\n className={cn(\n 'flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold tabular-nums',\n i === 0 ? 'bg-[var(--color-accent-soft)] text-text-primary' : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {i + 1}\n </span>\n <span className=\"w-48 shrink-0 truncate text-sm text-text-primary\">{c.title}</span>\n <MiniBar value={c.pct} barClassName=\"bg-[var(--color-accent-teal)]\" className=\"flex-1\" height={8} />\n <span className=\"w-10 shrink-0 text-right text-sm tabular-nums text-text-secondary\">\n {c.count}\n </span>\n </div>\n ))}\n </div>\n </GlassCard>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -7,172 +7,178 @@ import { ErrorView as o, LoadingView as s, Section as c } from "../../components
7
7
  import { Tabs as l } from "../../components/Tabs.js";
8
8
  import { MiniBar as u } from "../../components/MiniBar.js";
9
9
  import "../../context/index.js";
10
- import { useBuiCertificates as d, useBuiCourses as f, useBuiEnrollments as p } from "../../hooks/useBuildmyiqApi.js";
10
+ import { useBuiCertificates as d, useBuiCourses as f, useBuiEnrollments as p, useBuiSubmissions as m } from "../../hooks/useBuildmyiqApi.js";
11
11
  import "../../hooks/index.js";
12
- import { FileLink as m } from "../../components/FileLink.js";
12
+ import { FileLink as h } from "../../components/FileLink.js";
13
13
  import "../../components/index.js";
14
- import { useMemo as h, useState as g } from "react";
15
- import { ArrowRight as _, Award as v, GraduationCap as y, PlayCircle as b } from "lucide-react";
16
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
17
- import { Button as C, GlassCard as w } from "@burdenoff/fe-libs/ui";
14
+ import { useMemo as g, useState as _ } from "react";
15
+ import { ArrowRight as v, Award as y, GraduationCap as b, PlayCircle as x } from "lucide-react";
16
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
17
+ import { Button as w, GlassCard as T } from "@burdenoff/fe-libs/ui";
18
18
  //#region src/buildmyiq/pages/learning/MyLearningPage.tsx
19
- var T = new Set(["ENROLLED", "IN_PROGRESS"]), E = () => {
20
- let { currentUser: E, navigateTo: D } = e(), O = E?.id, [k, A] = g("in_progress"), j = p({
21
- learnerId: O,
19
+ var E = new Set(["ENROLLED", "IN_PROGRESS"]), D = () => {
20
+ let { currentUser: D, navigateTo: O } = e(), k = D?.id, [A, j] = _("in_progress"), M = p({
21
+ learnerId: k,
22
22
  limit: 100,
23
- enabled: !!O
24
- }), M = f({ limit: 100 }), N = d(void 0, O), P = h(() => j.data?.items ?? [], [j.data]), F = h(() => M.data?.items ?? [], [M.data]), I = h(() => N.data ?? [], [N.data]), L = h(() => {
23
+ enabled: !!k
24
+ }), N = f({ limit: 100 }), P = d(void 0, k), F = m({
25
+ learnerId: k,
26
+ limit: 50,
27
+ enabled: !!k
28
+ });
29
+ g(() => [...F.data?.items ?? []].sort((e, t) => (t.submittedAt ?? t.createdAt).localeCompare(e.submittedAt ?? e.createdAt)), [F.data]);
30
+ let I = g(() => M.data?.items ?? [], [M.data]), L = g(() => N.data?.items ?? [], [N.data]), R = g(() => P.data ?? [], [P.data]), z = g(() => {
25
31
  let e = /* @__PURE__ */ new Map();
26
- for (let t of F) e.set(t.id, t.title);
32
+ for (let t of L) e.set(t.id, t.title);
27
33
  return e;
28
- }, [F]), R = (e) => L.get(e) ?? "Course", z = h(() => P.filter((e) => T.has(e.status)), [P]), B = h(() => P.filter((e) => e.status === "COMPLETED"), [P]), V = h(() => [...k === "in_progress" ? z : k === "completed" ? B : P].sort((e, t) => (t.lastActiveAt ?? t.enrolledAt).localeCompare(e.lastActiveAt ?? e.enrolledAt)), [
29
- k,
30
- z,
31
- B,
32
- P
33
- ]), H = h(() => [...I].sort((e, t) => t.issuedAt.localeCompare(e.issuedAt)), [I]), U = [
34
+ }, [L]), B = (e) => z.get(e) ?? "Course", V = g(() => I.filter((e) => E.has(e.status)), [I]), H = g(() => I.filter((e) => e.status === "COMPLETED"), [I]), U = g(() => [...A === "in_progress" ? V : A === "completed" ? H : I].sort((e, t) => (t.lastActiveAt ?? t.enrolledAt).localeCompare(e.lastActiveAt ?? e.enrolledAt)), [
35
+ A,
36
+ V,
37
+ H,
38
+ I
39
+ ]), W = g(() => [...R].sort((e, t) => t.issuedAt.localeCompare(e.issuedAt)), [R]), G = [
34
40
  {
35
41
  value: "in_progress",
36
42
  label: "In progress",
37
- count: z.length
43
+ count: V.length
38
44
  },
39
45
  {
40
46
  value: "completed",
41
47
  label: "Completed",
42
- count: B.length
48
+ count: H.length
43
49
  },
44
50
  {
45
51
  value: "all",
46
52
  label: "All",
47
- count: P.length
53
+ count: I.length
48
54
  }
49
- ], W = !!O && (j.isLoading || M.isLoading);
50
- return /* @__PURE__ */ x(t, {
55
+ ], K = !!k && (M.isLoading || N.isLoading);
56
+ return /* @__PURE__ */ S(t, {
51
57
  title: "My Learning",
52
58
  description: "Your enrollments, progress and the credentials you've earned.",
53
59
  titleGradient: !0,
54
60
  aurora: !0,
55
- children: /* @__PURE__ */ S("div", {
61
+ children: /* @__PURE__ */ C("div", {
56
62
  className: "flex flex-col gap-6",
57
- children: [/* @__PURE__ */ S(c, {
63
+ children: [/* @__PURE__ */ C(c, {
58
64
  title: "Courses",
59
65
  description: "Everything you're enrolled in.",
60
- children: [O && P.length > 0 && /* @__PURE__ */ x("div", {
66
+ children: [k && I.length > 0 && /* @__PURE__ */ S("div", {
61
67
  className: "mb-4",
62
- children: /* @__PURE__ */ x(l, {
63
- items: U,
64
- value: k,
65
- onChange: A
68
+ children: /* @__PURE__ */ S(l, {
69
+ items: G,
70
+ value: A,
71
+ onChange: j
66
72
  })
67
- }), O ? W ? /* @__PURE__ */ x(s, { label: "Loading your enrollments…" }) : j.isError ? /* @__PURE__ */ x(o, {
73
+ }), k ? K ? /* @__PURE__ */ S(s, { label: "Loading your enrollments…" }) : M.isError ? /* @__PURE__ */ S(o, {
68
74
  title: "Couldn't load your learning",
69
- message: j.error instanceof Error ? j.error.message : void 0,
70
- onRetry: () => j.refetch()
71
- }) : P.length === 0 ? /* @__PURE__ */ x(a, {
75
+ message: M.error instanceof Error ? M.error.message : void 0,
76
+ onRetry: () => M.refetch()
77
+ }) : I.length === 0 ? /* @__PURE__ */ S(a, {
72
78
  illustration: "onboarding",
73
79
  title: "You're not enrolled in anything yet",
74
80
  description: "Browse the catalog and enrol in a course to start learning.",
75
- action: /* @__PURE__ */ x(C, {
76
- onClick: () => D("catalog"),
81
+ action: /* @__PURE__ */ S(w, {
82
+ onClick: () => O("catalog"),
77
83
  children: "Explore the catalog"
78
84
  })
79
- }) : V.length === 0 ? /* @__PURE__ */ x(a, {
85
+ }) : U.length === 0 ? /* @__PURE__ */ S(a, {
80
86
  illustration: "empty-data",
81
- title: k === "in_progress" ? "No courses in progress" : k === "completed" ? "No completed courses yet" : "No courses",
82
- description: k === "completed" ? "Finish a course to see it here — and earn a certificate." : "Switch tabs or enrol in a new course to keep learning."
83
- }) : /* @__PURE__ */ x("div", {
87
+ title: A === "in_progress" ? "No courses in progress" : A === "completed" ? "No completed courses yet" : "No courses",
88
+ description: A === "completed" ? "Finish a course to see it here — and earn a certificate." : "Switch tabs or enrol in a new course to keep learning."
89
+ }) : /* @__PURE__ */ S("div", {
84
90
  className: "flex flex-col gap-3",
85
- children: V.map((e) => /* @__PURE__ */ S(w, {
91
+ children: U.map((e) => /* @__PURE__ */ C(T, {
86
92
  treatment: "plain",
87
93
  className: "flex flex-col gap-3 p-4 sm:flex-row sm:items-center sm:gap-4",
88
94
  children: [
89
- /* @__PURE__ */ x("div", {
95
+ /* @__PURE__ */ S("div", {
90
96
  className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]",
91
- children: /* @__PURE__ */ x(b, { className: "h-5 w-5 text-text-primary" })
97
+ children: /* @__PURE__ */ S(x, { className: "h-5 w-5 text-text-primary" })
92
98
  }),
93
- /* @__PURE__ */ S("div", {
99
+ /* @__PURE__ */ C("div", {
94
100
  className: "min-w-0 flex-1",
95
101
  children: [
96
- /* @__PURE__ */ S("div", {
102
+ /* @__PURE__ */ C("div", {
97
103
  className: "flex flex-wrap items-center gap-2",
98
- children: [/* @__PURE__ */ x("p", {
104
+ children: [/* @__PURE__ */ S("p", {
99
105
  className: "truncate font-medium text-text-primary",
100
- children: R(e.courseId)
101
- }), /* @__PURE__ */ x(i, { status: e.status })]
106
+ children: B(e.courseId)
107
+ }), /* @__PURE__ */ S(i, { status: e.status })]
102
108
  }),
103
- /* @__PURE__ */ S("div", {
109
+ /* @__PURE__ */ C("div", {
104
110
  className: "mt-2 flex items-center gap-2",
105
- children: [/* @__PURE__ */ x(u, {
111
+ children: [/* @__PURE__ */ S(u, {
106
112
  value: e.progressPct,
107
113
  className: "max-w-sm flex-1"
108
- }), /* @__PURE__ */ S("span", {
114
+ }), /* @__PURE__ */ C("span", {
109
115
  className: "shrink-0 text-xs tabular-nums text-text-secondary",
110
116
  children: [Math.round(e.progressPct ?? 0), "%"]
111
117
  })]
112
118
  }),
113
- /* @__PURE__ */ x("p", {
119
+ /* @__PURE__ */ S("p", {
114
120
  className: "mt-1 text-xs text-text-secondary",
115
121
  children: e.status === "COMPLETED" && e.completedAt ? `Completed ${r(e.completedAt)}` : `Active ${r(e.lastActiveAt ?? e.enrolledAt)}`
116
122
  })
117
123
  ]
118
124
  }),
119
- /* @__PURE__ */ S(C, {
125
+ /* @__PURE__ */ C(w, {
120
126
  variant: "secondary",
121
127
  size: "sm",
122
128
  className: "shrink-0 max-sm:w-full",
123
- onClick: () => D(`learning/${e.id}`),
124
- children: [e.status === "COMPLETED" ? "Review" : "Continue", /* @__PURE__ */ x(_, { className: "ml-1 h-4 w-4" })]
129
+ onClick: () => O(`learning/${e.id}`),
130
+ children: [e.status === "COMPLETED" ? "Review" : "Continue", /* @__PURE__ */ S(v, { className: "ml-1 h-4 w-4" })]
125
131
  })
126
132
  ]
127
133
  }, e.id))
128
- }) : /* @__PURE__ */ x(a, {
134
+ }) : /* @__PURE__ */ S(a, {
129
135
  illustration: "empty-people",
130
136
  title: "Sign in to see your learning",
131
137
  description: "Your enrollments and progress appear here once you are signed in to the workspace."
132
138
  })]
133
- }), /* @__PURE__ */ x(c, {
139
+ }), /* @__PURE__ */ S(c, {
134
140
  title: "Certificates",
135
141
  description: "Verifiable credentials issued for the courses you've completed.",
136
- action: /* @__PURE__ */ S(C, {
142
+ action: /* @__PURE__ */ C(w, {
137
143
  variant: "ghost",
138
144
  size: "sm",
139
- onClick: () => D("certificates"),
140
- children: [/* @__PURE__ */ x(y, { className: "mr-1 h-4 w-4" }), " All certificates"]
145
+ onClick: () => O("certificates"),
146
+ children: [/* @__PURE__ */ S(b, { className: "mr-1 h-4 w-4" }), " All certificates"]
141
147
  }),
142
- children: O ? N.isLoading ? /* @__PURE__ */ x(s, { label: "Loading your certificates…" }) : N.isError ? /* @__PURE__ */ x(o, {
148
+ children: k ? P.isLoading ? /* @__PURE__ */ S(s, { label: "Loading your certificates…" }) : P.isError ? /* @__PURE__ */ S(o, {
143
149
  title: "Couldn't load your certificates",
144
- message: N.error instanceof Error ? N.error.message : void 0,
145
- onRetry: () => N.refetch()
146
- }) : H.length === 0 ? /* @__PURE__ */ x(a, {
150
+ message: P.error instanceof Error ? P.error.message : void 0,
151
+ onRetry: () => P.refetch()
152
+ }) : W.length === 0 ? /* @__PURE__ */ S(a, {
147
153
  illustration: "empty-data",
148
154
  title: "No certificates yet",
149
155
  description: "Complete a credentialed course to earn a verifiable certificate."
150
- }) : /* @__PURE__ */ x("div", {
156
+ }) : /* @__PURE__ */ S("div", {
151
157
  className: "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3",
152
- children: H.map((e) => /* @__PURE__ */ S(w, {
158
+ children: W.map((e) => /* @__PURE__ */ C(T, {
153
159
  treatment: "plain",
154
160
  className: "flex flex-col gap-3 p-4",
155
161
  children: [
156
- /* @__PURE__ */ S("div", {
162
+ /* @__PURE__ */ C("div", {
157
163
  className: "flex items-start justify-between gap-3",
158
- children: [/* @__PURE__ */ x("div", {
164
+ children: [/* @__PURE__ */ S("div", {
159
165
  className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]",
160
- children: /* @__PURE__ */ x(v, { className: "h-5 w-5 text-text-primary" })
161
- }), /* @__PURE__ */ x(i, { status: e.status })]
166
+ children: /* @__PURE__ */ S(y, { className: "h-5 w-5 text-text-primary" })
167
+ }), /* @__PURE__ */ S(i, { status: e.status })]
162
168
  }),
163
- /* @__PURE__ */ S("div", {
169
+ /* @__PURE__ */ C("div", {
164
170
  className: "min-w-0",
165
- children: [/* @__PURE__ */ x("p", {
171
+ children: [/* @__PURE__ */ S("p", {
166
172
  className: "truncate font-medium text-text-primary",
167
- children: R(e.courseId)
168
- }), /* @__PURE__ */ x("p", {
173
+ children: B(e.courseId)
174
+ }), /* @__PURE__ */ S("p", {
169
175
  className: "mt-0.5 font-mono text-xs text-text-secondary",
170
176
  children: e.serial
171
177
  })]
172
178
  }),
173
- /* @__PURE__ */ S("div", {
179
+ /* @__PURE__ */ C("div", {
174
180
  className: "mt-auto flex flex-wrap items-center justify-between gap-2 pt-1 text-xs text-text-secondary",
175
- children: [/* @__PURE__ */ S("span", { children: ["Issued ", n(e.issuedAt)] }), e.file ? /* @__PURE__ */ x(m, { fileId: e.file.id }) : null]
181
+ children: [/* @__PURE__ */ C("span", { children: ["Issued ", n(e.issuedAt)] }), e.file ? /* @__PURE__ */ S(h, { fileId: e.file.id }) : null]
176
182
  })
177
183
  ]
178
184
  }, e.id))
@@ -182,6 +188,6 @@ var T = new Set(["ENROLLED", "IN_PROGRESS"]), E = () => {
182
188
  });
183
189
  };
184
190
  //#endregion
185
- export { E as MyLearningPage };
191
+ export { D as MyLearningPage };
186
192
 
187
193
  //# sourceMappingURL=MyLearningPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MyLearningPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/learning/MyLearningPage.tsx"],"sourcesContent":["/**\n * MyLearningPage — the caller's personal learning surface.\n * @module buildmyiq/pages/learning\n *\n * Renders the current learner's real enrollments (`buiListEnrollments` filtered\n * by `learnerId`) grouped by progress, plus the credentials they have earned\n * (`buiListCertificates`). Course titles are resolved from `buiListCourses`\n * (id → title map, falling back to \"Course\"). Every list carries an honest\n * loading / error / empty state — no fabricated rows.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { GraduationCap, Award, ArrowRight, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n Tabs,\n StatusBadge,\n MiniBar,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n FileLink,\n} from '../../components';\nimport type { TabItem } from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiEnrollments, useBuiCourses, useBuiCertificates } from '../../hooks';\nimport { formatRelativeTime, formatDate } from '../../utils/formatters';\nimport type { BuiEnrollment, BuiCourse, BuiCertificate } from '../../types';\n\ntype LearningTab = 'in_progress' | 'completed' | 'all';\n\nconst IN_PROGRESS_STATUSES = new Set(['ENROLLED', 'IN_PROGRESS']);\n\nexport const MyLearningPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const [tab, setTab] = useState<LearningTab>('in_progress');\n\n const enrollmentsQuery = useBuiEnrollments({\n learnerId,\n limit: 100,\n enabled: Boolean(learnerId),\n });\n const coursesQuery = useBuiCourses({ limit: 100 });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const enrollments: BuiEnrollment[] = useMemo(\n () => enrollmentsQuery.data?.items ?? [],\n [enrollmentsQuery.data]\n );\n const courses: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const certificates: BuiCertificate[] = useMemo(() => certsQuery.data ?? [], [certsQuery.data]);\n\n const courseTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const c of courses) map.set(c.id, c.title);\n return map;\n }, [courses]);\n\n const titleFor = (courseId: string): string => courseTitleById.get(courseId) ?? 'Course';\n\n const inProgress = useMemo(\n () => enrollments.filter((e) => IN_PROGRESS_STATUSES.has(e.status)),\n [enrollments]\n );\n const completed = useMemo(\n () => enrollments.filter((e) => e.status === 'COMPLETED'),\n [enrollments]\n );\n\n const visible = useMemo(() => {\n const rows = tab === 'in_progress' ? inProgress : tab === 'completed' ? completed : enrollments;\n return [...rows].sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n );\n }, [tab, inProgress, completed, enrollments]);\n\n const sortedCertificates = useMemo(\n () => [...certificates].sort((a, b) => b.issuedAt.localeCompare(a.issuedAt)),\n [certificates]\n );\n\n const tabs: TabItem<LearningTab>[] = [\n { value: 'in_progress', label: 'In progress', count: inProgress.length },\n { value: 'completed', label: 'Completed', count: completed.length },\n { value: 'all', label: 'All', count: enrollments.length },\n ];\n\n const isLoading = Boolean(learnerId) && (enrollmentsQuery.isLoading || coursesQuery.isLoading);\n\n const renderEnrollments = () => {\n if (!learnerId) {\n return (\n <EmptyState\n illustration=\"empty-people\"\n title=\"Sign in to see your learning\"\n description=\"Your enrollments and progress appear here once you are signed in to the workspace.\"\n />\n );\n }\n if (isLoading) return <LoadingView label=\"Loading your enrollments…\" />;\n if (enrollmentsQuery.isError) {\n return (\n <ErrorView\n title=\"Couldn't load your learning\"\n message={\n enrollmentsQuery.error instanceof Error ? enrollmentsQuery.error.message : undefined\n }\n onRetry={() => enrollmentsQuery.refetch()}\n />\n );\n }\n if (enrollments.length === 0) {\n return (\n <EmptyState\n illustration=\"onboarding\"\n title=\"You're not enrolled in anything yet\"\n description=\"Browse the catalog and enrol in a course to start learning.\"\n action={<Button onClick={() => navigateTo('catalog')}>Explore the catalog</Button>}\n />\n );\n }\n if (visible.length === 0) {\n const label =\n tab === 'in_progress'\n ? 'No courses in progress'\n : tab === 'completed'\n ? 'No completed courses yet'\n : 'No courses';\n return (\n <EmptyState\n illustration=\"empty-data\"\n title={label}\n description={\n tab === 'completed'\n ? 'Finish a course to see it here — and earn a certificate.'\n : 'Switch tabs or enrol in a new course to keep learning.'\n }\n />\n );\n }\n return (\n <div className=\"flex flex-col gap-3\">\n {visible.map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex flex-col gap-3 p-4 sm:flex-row sm:items-center sm:gap-4\"\n >\n <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <PlayCircle className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"truncate font-medium text-text-primary\">{titleFor(e.courseId)}</p>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"max-w-sm flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {Math.round(e.progressPct ?? 0)}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {e.status === 'COMPLETED' && e.completedAt\n ? `Completed ${formatRelativeTime(e.completedAt)}`\n : `Active ${formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}`}\n </p>\n </div>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"shrink-0 max-sm:w-full\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n {e.status === 'COMPLETED' ? 'Review' : 'Continue'}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </GlassCard>\n ))}\n </div>\n );\n };\n\n const renderCertificates = () => {\n if (!learnerId) return null;\n if (certsQuery.isLoading) return <LoadingView label=\"Loading your certificates…\" />;\n if (certsQuery.isError) {\n return (\n <ErrorView\n title=\"Couldn't load your certificates\"\n message={certsQuery.error instanceof Error ? certsQuery.error.message : undefined}\n onRetry={() => certsQuery.refetch()}\n />\n );\n }\n if (sortedCertificates.length === 0) {\n return (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No certificates yet\"\n description=\"Complete a credentialed course to earn a verifiable certificate.\"\n />\n );\n }\n return (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {sortedCertificates.map((cert) => (\n <GlassCard key={cert.id} treatment=\"plain\" className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <Award className=\"h-5 w-5 text-text-primary\" />\n </div>\n <StatusBadge status={cert.status} />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{titleFor(cert.courseId)}</p>\n <p className=\"mt-0.5 font-mono text-xs text-text-secondary\">{cert.serial}</p>\n </div>\n <div className=\"mt-auto flex flex-wrap items-center justify-between gap-2 pt-1 text-xs text-text-secondary\">\n <span>Issued {formatDate(cert.issuedAt)}</span>\n {cert.file ? <FileLink fileId={cert.file.id} /> : null}\n </div>\n </GlassCard>\n ))}\n </div>\n );\n };\n\n return (\n <PageLayout\n title=\"My Learning\"\n description=\"Your enrollments, progress and the credentials you've earned.\"\n titleGradient\n aurora\n >\n <div className=\"flex flex-col gap-6\">\n <Section title=\"Courses\" description=\"Everything you're enrolled in.\">\n {learnerId && enrollments.length > 0 && (\n <div className=\"mb-4\">\n <Tabs items={tabs} value={tab} onChange={setTab} />\n </div>\n )}\n {renderEnrollments()}\n </Section>\n\n <Section\n title=\"Certificates\"\n description=\"Verifiable credentials issued for the courses you've completed.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('certificates')}>\n <GraduationCap className=\"mr-1 h-4 w-4\" /> All certificates\n </Button>\n }\n >\n {renderCertificates()}\n </Section>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"MyLearningPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/learning/MyLearningPage.tsx"],"sourcesContent":["/**\n * MyLearningPage — the caller's personal learning surface.\n * @module buildmyiq/pages/learning\n *\n * Renders the current learner's real enrollments (`buiListEnrollments` filtered\n * by `learnerId`) grouped by progress, plus the credentials they have earned\n * (`buiListCertificates`). Course titles are resolved from `buiListCourses`\n * (id → title map, falling back to \"Course\"). Every list carries an honest\n * loading / error / empty state — no fabricated rows.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { GraduationCap, Award, ArrowRight, PlayCircle, ClipboardCheck } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n Tabs,\n StatusBadge,\n MiniBar,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n FileLink,\n} from '../../components';\nimport type { TabItem } from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiEnrollments,\n useBuiCourses,\n useBuiCertificates,\n useBuiSubmissions,\n} from '../../hooks';\nimport { formatRelativeTime, formatDate } from '../../utils/formatters';\nimport type { BuiEnrollment, BuiCourse, BuiCertificate } from '../../types';\n\ntype LearningTab = 'in_progress' | 'completed' | 'all';\n\nconst IN_PROGRESS_STATUSES = new Set(['ENROLLED', 'IN_PROGRESS']);\n\nexport const MyLearningPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const [tab, setTab] = useState<LearningTab>('in_progress');\n\n const enrollmentsQuery = useBuiEnrollments({\n learnerId,\n limit: 100,\n enabled: Boolean(learnerId),\n });\n const coursesQuery = useBuiCourses({ limit: 100 });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n const submissionsQuery = useBuiSubmissions({\n learnerId,\n limit: 50,\n enabled: Boolean(learnerId),\n });\n const submissions = useMemo(\n () =>\n [...(submissionsQuery.data?.items ?? [])].sort((a, b) =>\n (b.submittedAt ?? b.createdAt).localeCompare(a.submittedAt ?? a.createdAt)\n ),\n [submissionsQuery.data]\n );\n\n const enrollments: BuiEnrollment[] = useMemo(\n () => enrollmentsQuery.data?.items ?? [],\n [enrollmentsQuery.data]\n );\n const courses: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const certificates: BuiCertificate[] = useMemo(() => certsQuery.data ?? [], [certsQuery.data]);\n\n const courseTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const c of courses) map.set(c.id, c.title);\n return map;\n }, [courses]);\n\n const titleFor = (courseId: string): string => courseTitleById.get(courseId) ?? 'Course';\n\n const inProgress = useMemo(\n () => enrollments.filter((e) => IN_PROGRESS_STATUSES.has(e.status)),\n [enrollments]\n );\n const completed = useMemo(\n () => enrollments.filter((e) => e.status === 'COMPLETED'),\n [enrollments]\n );\n\n const visible = useMemo(() => {\n const rows = tab === 'in_progress' ? inProgress : tab === 'completed' ? completed : enrollments;\n return [...rows].sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n );\n }, [tab, inProgress, completed, enrollments]);\n\n const sortedCertificates = useMemo(\n () => [...certificates].sort((a, b) => b.issuedAt.localeCompare(a.issuedAt)),\n [certificates]\n );\n\n const tabs: TabItem<LearningTab>[] = [\n { value: 'in_progress', label: 'In progress', count: inProgress.length },\n { value: 'completed', label: 'Completed', count: completed.length },\n { value: 'all', label: 'All', count: enrollments.length },\n ];\n\n const isLoading = Boolean(learnerId) && (enrollmentsQuery.isLoading || coursesQuery.isLoading);\n\n const renderEnrollments = () => {\n if (!learnerId) {\n return (\n <EmptyState\n illustration=\"empty-people\"\n title=\"Sign in to see your learning\"\n description=\"Your enrollments and progress appear here once you are signed in to the workspace.\"\n />\n );\n }\n if (isLoading) return <LoadingView label=\"Loading your enrollments…\" />;\n if (enrollmentsQuery.isError) {\n return (\n <ErrorView\n title=\"Couldn't load your learning\"\n message={\n enrollmentsQuery.error instanceof Error ? enrollmentsQuery.error.message : undefined\n }\n onRetry={() => enrollmentsQuery.refetch()}\n />\n );\n }\n if (enrollments.length === 0) {\n return (\n <EmptyState\n illustration=\"onboarding\"\n title=\"You're not enrolled in anything yet\"\n description=\"Browse the catalog and enrol in a course to start learning.\"\n action={<Button onClick={() => navigateTo('catalog')}>Explore the catalog</Button>}\n />\n );\n }\n if (visible.length === 0) {\n const label =\n tab === 'in_progress'\n ? 'No courses in progress'\n : tab === 'completed'\n ? 'No completed courses yet'\n : 'No courses';\n return (\n <EmptyState\n illustration=\"empty-data\"\n title={label}\n description={\n tab === 'completed'\n ? 'Finish a course to see it here — and earn a certificate.'\n : 'Switch tabs or enrol in a new course to keep learning.'\n }\n />\n );\n }\n return (\n <div className=\"flex flex-col gap-3\">\n {visible.map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex flex-col gap-3 p-4 sm:flex-row sm:items-center sm:gap-4\"\n >\n <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <PlayCircle className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"truncate font-medium text-text-primary\">{titleFor(e.courseId)}</p>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"max-w-sm flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {Math.round(e.progressPct ?? 0)}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {e.status === 'COMPLETED' && e.completedAt\n ? `Completed ${formatRelativeTime(e.completedAt)}`\n : `Active ${formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}`}\n </p>\n </div>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"shrink-0 max-sm:w-full\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n {e.status === 'COMPLETED' ? 'Review' : 'Continue'}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </GlassCard>\n ))}\n </div>\n );\n };\n\n const renderCertificates = () => {\n if (!learnerId) return null;\n if (certsQuery.isLoading) return <LoadingView label=\"Loading your certificates…\" />;\n if (certsQuery.isError) {\n return (\n <ErrorView\n title=\"Couldn't load your certificates\"\n message={certsQuery.error instanceof Error ? certsQuery.error.message : undefined}\n onRetry={() => certsQuery.refetch()}\n />\n );\n }\n if (sortedCertificates.length === 0) {\n return (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No certificates yet\"\n description=\"Complete a credentialed course to earn a verifiable certificate.\"\n />\n );\n }\n return (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {sortedCertificates.map((cert) => (\n <GlassCard key={cert.id} treatment=\"plain\" className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <Award className=\"h-5 w-5 text-text-primary\" />\n </div>\n <StatusBadge status={cert.status} />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{titleFor(cert.courseId)}</p>\n <p className=\"mt-0.5 font-mono text-xs text-text-secondary\">{cert.serial}</p>\n </div>\n <div className=\"mt-auto flex flex-wrap items-center justify-between gap-2 pt-1 text-xs text-text-secondary\">\n <span>Issued {formatDate(cert.issuedAt)}</span>\n {cert.file ? <FileLink fileId={cert.file.id} /> : null}\n </div>\n </GlassCard>\n ))}\n </div>\n );\n };\n\n const renderResults = () => {\n if (!learnerId) return null;\n if (submissionsQuery.isLoading) return <LoadingView label=\"Loading your results…\" />;\n if (submissionsQuery.isError)\n return (\n <ErrorView\n title=\"Couldn't load your results\"\n message={submissionsQuery.error instanceof Error ? submissionsQuery.error.message : undefined}\n onRetry={() => submissionsQuery.refetch()}\n />\n );\n if (submissions.length === 0)\n return (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assessment results yet\"\n description=\"Take a course assessment and your score and feedback will appear here.\"\n />\n );\n return (\n <div className=\"flex flex-col gap-3\">\n {submissions.map((s) => (\n <GlassCard key={s.id} treatment=\"plain\" className=\"flex flex-wrap items-center gap-3 p-4\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <ClipboardCheck className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-medium text-text-primary\">Attempt {s.attempt}</p>\n <StatusBadge status={s.status} />\n {s.passed != null && (\n <span\n className={`text-xs font-medium ${s.passed ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {s.passed ? 'Passed' : 'Not passed'}\n </span>\n )}\n </div>\n {s.feedback && <p className=\"mt-1 text-sm text-text-secondary\">{s.feedback}</p>}\n <p className=\"mt-1 text-xs text-text-secondary\">\n Submitted {formatDate(s.submittedAt ?? s.createdAt)}\n {s.gradedAt ? ` · graded ${formatDate(s.gradedAt)}` : ''}\n </p>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {s.score != null ? `${s.score}%` : '—'}\n </p>\n <p className=\"text-xs text-text-secondary\">score</p>\n </div>\n </GlassCard>\n ))}\n </div>\n );\n };\n\n return (\n <PageLayout\n title=\"My Learning\"\n description=\"Your enrollments, progress and the credentials you've earned.\"\n titleGradient\n aurora\n >\n <div className=\"flex flex-col gap-6\">\n <Section title=\"Courses\" description=\"Everything you're enrolled in.\">\n {learnerId && enrollments.length > 0 && (\n <div className=\"mb-4\">\n <Tabs items={tabs} value={tab} onChange={setTab} />\n </div>\n )}\n {renderEnrollments()}\n </Section>\n\n <Section\n title=\"Certificates\"\n description=\"Verifiable credentials issued for the courses you've completed.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('certificates')}>\n <GraduationCap className=\"mr-1 h-4 w-4\" /> All certificates\n </Button>\n }\n >\n {renderCertificates()}\n </Section>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-buildmyiq",
3
- "version": "2026.705.9",
3
+ "version": "2026.705.11",
4
4
  "description": "Burdenoff BuildMyIQ Microfrontend — education, learning & development",
5
5
  "type": "module",
6
6
  "files": [