@burdenoff/microfe-buildmyiq 2026.705.13 → 2026.706.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buildmyiq/BuildmyiqRoot.d.ts.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoot.js +57 -49
- package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoutes.d.ts.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoutes.js +15 -11
- package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
- package/dist/buildmyiq/components/Avatar.d.ts +15 -0
- package/dist/buildmyiq/components/Avatar.d.ts.map +1 -0
- package/dist/buildmyiq/components/Avatar.js +42 -0
- package/dist/buildmyiq/components/Avatar.js.map +1 -0
- package/dist/buildmyiq/components/Viz.d.ts +25 -0
- package/dist/buildmyiq/components/Viz.d.ts.map +1 -0
- package/dist/buildmyiq/components/Viz.js +78 -0
- package/dist/buildmyiq/components/Viz.js.map +1 -0
- package/dist/buildmyiq/components/index.d.ts +2 -0
- package/dist/buildmyiq/components/index.d.ts.map +1 -1
- package/dist/buildmyiq/components/index.js +2 -0
- package/dist/buildmyiq/pages/DashboardPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/DashboardPage.js +200 -98
- package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js +262 -241
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.js +142 -115
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +220 -156
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js +51 -46
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +581 -448
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/catalog/CatalogPage.js +52 -42
- package/dist/buildmyiq/pages/catalog/CatalogPage.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +422 -301
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js +237 -151
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js +219 -138
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
- package/dist/buildmyiq/pages/insights/InsightsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/insights/InsightsPage.js +276 -152
- package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +237 -174
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
- package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/learning/MyLearningPage.js +307 -169
- package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
- package/dist/buildmyiq/pages/paths/PathsPage.d.ts +4 -0
- package/dist/buildmyiq/pages/paths/PathsPage.d.ts.map +1 -0
- package/dist/buildmyiq/pages/paths/PathsPage.js +187 -0
- package/dist/buildmyiq/pages/paths/PathsPage.js.map +1 -0
- package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/skills/SkillsPage.js +233 -140
- package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +379 -275
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,143 +1,245 @@
|
|
|
1
1
|
import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../components/PageLayout.js";
|
|
4
|
+
import { MetricsCard as r } from "../components/MetricsCard.js";
|
|
5
|
+
import { formatEnumLabel as i, formatRelativeTime as a } from "../utils/formatters.js";
|
|
6
|
+
import { EmptyState as o } from "../components/EmptyState.js";
|
|
7
|
+
import { LoadingView as s, Section as c } from "../components/StateViews.js";
|
|
8
|
+
import { MiniBar as l } from "../components/MiniBar.js";
|
|
9
|
+
import { Avatar as u } from "../components/Avatar.js";
|
|
10
|
+
import { Sparkline as d } from "../components/Viz.js";
|
|
8
11
|
import "../context/index.js";
|
|
9
|
-
import { useBuiCertificates as
|
|
12
|
+
import { useBuiCertificates as f, useBuiCourses as p, useBuiEnrollments as m } from "../hooks/useBuildmyiqApi.js";
|
|
10
13
|
import "../hooks/index.js";
|
|
11
14
|
import "../components/index.js";
|
|
12
|
-
import { CourseCard as
|
|
15
|
+
import { CourseCard as h } from "./_shared/CourseCard.js";
|
|
13
16
|
import "./_shared/index.js";
|
|
14
|
-
import { useMemo as
|
|
15
|
-
import { ArrowRight as
|
|
16
|
-
import { jsx as
|
|
17
|
-
import { Button as
|
|
17
|
+
import { useMemo as g } from "react";
|
|
18
|
+
import { ArrowRight as _, Award as v, BookOpen as y, CheckCircle2 as b, Clock as x, Play as S } from "lucide-react";
|
|
19
|
+
import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
20
|
+
import { Button as T, GlassCard as E } from "@burdenoff/fe-libs/ui";
|
|
18
21
|
//#region src/buildmyiq/pages/DashboardPage.tsx
|
|
19
|
-
var
|
|
20
|
-
let
|
|
22
|
+
var D = (e, t = 8) => {
|
|
23
|
+
let n = e.map((e) => e ? new Date(e).getTime() : NaN).filter((e) => !Number.isNaN(e)).sort((e, t) => e - t);
|
|
24
|
+
if (n.length < 2) return n.length === 1 ? [1] : [];
|
|
25
|
+
let r = n[0], i = Math.max(1, Date.now() - r), a = [];
|
|
26
|
+
for (let e = 1; e <= t; e += 1) {
|
|
27
|
+
let o = r + i * e / t;
|
|
28
|
+
a.push(n.filter((e) => e <= o).length);
|
|
29
|
+
}
|
|
30
|
+
return a;
|
|
31
|
+
}, O = ({ tint: e, children: n }) => /* @__PURE__ */ C("span", {
|
|
32
|
+
className: t("inline-flex h-10 w-10 items-center justify-center rounded-xl", e),
|
|
33
|
+
"aria-hidden": "true",
|
|
34
|
+
children: n
|
|
35
|
+
}), k = () => {
|
|
36
|
+
let { currentUser: t, navigateTo: k } = e(), A = t?.id, j = p({
|
|
21
37
|
status: "PUBLISHED",
|
|
22
38
|
limit: 50
|
|
23
|
-
}),
|
|
24
|
-
learnerId:
|
|
39
|
+
}), M = m({
|
|
40
|
+
learnerId: A,
|
|
25
41
|
limit: 50,
|
|
26
|
-
enabled: !!
|
|
27
|
-
}),
|
|
42
|
+
enabled: !!A
|
|
43
|
+
}), N = f(void 0, A), P = j.data?.items ?? [], F = M.data?.items ?? [], I = N.data ?? [], L = g(() => F.filter((e) => e.status === "ENROLLED" || e.status === "IN_PROGRESS").sort((e, t) => (t.lastActiveAt ?? t.enrolledAt).localeCompare(e.lastActiveAt ?? e.enrolledAt)), [F]), R = F.filter((e) => e.status === "COMPLETED").length, z = t?.firstName || t?.name?.split(" ")[0] || "there", B = [t?.firstName, t?.lastName].filter(Boolean).join(" ").trim(), V = t?.name || B || (z === "there" ? "" : z), H = t?.role ? i(t.role) : t?.email, U = j.isLoading || !!A && M.isLoading, W = g(() => {
|
|
28
44
|
let e = /* @__PURE__ */ new Map();
|
|
29
|
-
for (let t of
|
|
45
|
+
for (let t of P) e.set(t.id, t);
|
|
30
46
|
return e;
|
|
31
|
-
}, [
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
}, [P]), G = g(() => ({
|
|
48
|
+
published: D(P.map((e) => e.publishedAt ?? e.createdAt)),
|
|
49
|
+
inProgress: D(F.map((e) => e.enrolledAt)),
|
|
50
|
+
completed: D(F.filter((e) => e.status === "COMPLETED").map((e) => e.completedAt ?? e.updatedAt)),
|
|
51
|
+
certificates: D(I.map((e) => e.issuedAt))
|
|
52
|
+
}), [
|
|
53
|
+
P,
|
|
54
|
+
F,
|
|
55
|
+
I
|
|
56
|
+
]), K = [
|
|
57
|
+
{
|
|
58
|
+
key: "published",
|
|
59
|
+
icon: /* @__PURE__ */ C(y, { className: "h-5 w-5" }),
|
|
60
|
+
tint: "bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]",
|
|
61
|
+
value: `${P.length}${j.data?.pageInfo?.hasMore ? "+" : ""}`,
|
|
62
|
+
label: "Published courses",
|
|
63
|
+
featured: !0,
|
|
64
|
+
series: G.published,
|
|
65
|
+
sparkColor: "var(--color-accent-primary)"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: "in-progress",
|
|
69
|
+
icon: /* @__PURE__ */ C(x, { className: "h-5 w-5" }),
|
|
70
|
+
tint: "bg-[color-mix(in_srgb,var(--color-accent-teal)_16%,transparent)] text-[var(--color-accent-teal)]",
|
|
71
|
+
value: L.length,
|
|
72
|
+
label: "Courses in progress",
|
|
73
|
+
series: G.inProgress,
|
|
74
|
+
sparkColor: "var(--color-accent-teal)"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: "completed",
|
|
78
|
+
icon: /* @__PURE__ */ C(b, { className: "h-5 w-5" }),
|
|
79
|
+
tint: "bg-status-success-bg-subtle text-status-success-text",
|
|
80
|
+
value: R,
|
|
81
|
+
label: "Completed",
|
|
82
|
+
series: G.completed,
|
|
83
|
+
sparkColor: "var(--color-status-success-text)"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "certificates",
|
|
87
|
+
icon: /* @__PURE__ */ C(v, { className: "h-5 w-5" }),
|
|
88
|
+
tint: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
89
|
+
value: I.length,
|
|
90
|
+
label: "Certificates earned",
|
|
91
|
+
series: G.certificates,
|
|
92
|
+
sparkColor: "var(--color-status-warning-text)"
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
return /* @__PURE__ */ C(n, {
|
|
96
|
+
title: `Welcome back, ${z}`,
|
|
34
97
|
description: "Your learning, teaching and credentialing — all in one workspace.",
|
|
35
98
|
titleGradient: !0,
|
|
36
99
|
aurora: !0,
|
|
37
|
-
|
|
100
|
+
actions: t ? /* @__PURE__ */ w("div", {
|
|
101
|
+
className: "flex w-fit items-center gap-2.5 rounded-full border border-border-seam bg-bg-surface/60 py-1.5 pl-1.5 pr-3.5 backdrop-blur",
|
|
102
|
+
children: [/* @__PURE__ */ C(u, {
|
|
103
|
+
name: V || null,
|
|
104
|
+
seed: t.id,
|
|
105
|
+
src: t.avatar,
|
|
106
|
+
size: "sm"
|
|
107
|
+
}), (V || H) && /* @__PURE__ */ w("div", {
|
|
108
|
+
className: "min-w-0 leading-tight",
|
|
109
|
+
children: [V && /* @__PURE__ */ C("p", {
|
|
110
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
111
|
+
children: V
|
|
112
|
+
}), H && /* @__PURE__ */ C("p", {
|
|
113
|
+
className: "max-w-[10rem] truncate text-xs text-text-secondary",
|
|
114
|
+
children: H
|
|
115
|
+
})]
|
|
116
|
+
})]
|
|
117
|
+
}) : void 0,
|
|
118
|
+
children: U ? /* @__PURE__ */ C(s, { label: "Loading your learning dashboard…" }) : /* @__PURE__ */ w("div", {
|
|
38
119
|
className: "flex flex-col gap-8",
|
|
39
120
|
children: [
|
|
40
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ C("div", {
|
|
41
122
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4",
|
|
42
|
-
children:
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
label: "Published courses",
|
|
47
|
-
featured: !0
|
|
123
|
+
children: K.map((e) => /* @__PURE__ */ C(r, {
|
|
124
|
+
icon: /* @__PURE__ */ C(O, {
|
|
125
|
+
tint: e.tint,
|
|
126
|
+
children: e.icon
|
|
48
127
|
}),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
label: "Completed",
|
|
58
|
-
accent: "success"
|
|
59
|
-
}),
|
|
60
|
-
/* @__PURE__ */ y(n, {
|
|
61
|
-
icon: /* @__PURE__ */ y(m, { className: "h-5 w-5" }),
|
|
62
|
-
value: j.length,
|
|
63
|
-
label: "Certificates earned"
|
|
64
|
-
})
|
|
65
|
-
]
|
|
128
|
+
value: e.value,
|
|
129
|
+
label: e.label,
|
|
130
|
+
featured: e.featured,
|
|
131
|
+
pill: e.series.length >= 2 ? /* @__PURE__ */ C(d, {
|
|
132
|
+
data: e.series,
|
|
133
|
+
color: e.sparkColor
|
|
134
|
+
}) : void 0
|
|
135
|
+
}, e.key))
|
|
66
136
|
}),
|
|
67
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ C(c, {
|
|
68
138
|
title: "Continue learning",
|
|
69
139
|
description: "Pick up where you left off.",
|
|
70
|
-
action: /* @__PURE__ */
|
|
140
|
+
action: /* @__PURE__ */ w(T, {
|
|
71
141
|
variant: "ghost",
|
|
72
142
|
size: "sm",
|
|
73
|
-
onClick: () =>
|
|
74
|
-
children: ["My learning ", /* @__PURE__ */
|
|
143
|
+
onClick: () => k("learning"),
|
|
144
|
+
children: ["My learning ", /* @__PURE__ */ C(_, { className: "ml-1 h-4 w-4" })]
|
|
75
145
|
}),
|
|
76
|
-
children:
|
|
146
|
+
children: L.length === 0 ? /* @__PURE__ */ C(o, {
|
|
77
147
|
illustration: "onboarding",
|
|
78
148
|
title: "No active courses yet",
|
|
79
149
|
description: "Browse the catalog and enrol in a course to start learning.",
|
|
80
|
-
action: /* @__PURE__ */
|
|
81
|
-
onClick: () =>
|
|
150
|
+
action: /* @__PURE__ */ C(T, {
|
|
151
|
+
onClick: () => k("catalog"),
|
|
82
152
|
children: "Explore the catalog"
|
|
83
153
|
})
|
|
84
|
-
}) : /* @__PURE__ */
|
|
85
|
-
className: "grid grid-cols-1 gap-
|
|
86
|
-
children:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/* @__PURE__ */
|
|
101
|
-
className: "
|
|
102
|
-
children:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
className: "
|
|
107
|
-
children:
|
|
108
|
-
})
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
154
|
+
}) : /* @__PURE__ */ C("div", {
|
|
155
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
156
|
+
children: L.slice(0, 4).map((e) => {
|
|
157
|
+
let t = W.get(e.courseId), n = t?.title ?? "Course", r = (t) => {
|
|
158
|
+
t?.stopPropagation(), k(`learning/${e.id}`);
|
|
159
|
+
};
|
|
160
|
+
return /* @__PURE__ */ w(E, {
|
|
161
|
+
treatment: "plain",
|
|
162
|
+
className: "group flex cursor-pointer flex-col overflow-hidden transition-shadow hover:shadow-lg sm:flex-row",
|
|
163
|
+
onClick: () => r(),
|
|
164
|
+
children: [/* @__PURE__ */ w("div", {
|
|
165
|
+
className: "relative h-36 w-full shrink-0 overflow-hidden bg-bg-sunken sm:h-auto sm:w-40",
|
|
166
|
+
children: [t?.cover?.downloadUrl ? /* @__PURE__ */ C("img", {
|
|
167
|
+
src: t.cover.downloadUrl,
|
|
168
|
+
alt: "",
|
|
169
|
+
className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
|
170
|
+
}) : /* @__PURE__ */ C("div", {
|
|
171
|
+
className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-[var(--color-accent-soft)] to-bg-sunken",
|
|
172
|
+
children: /* @__PURE__ */ C(y, { className: "h-8 w-8 text-text-secondary opacity-60" })
|
|
173
|
+
}), /* @__PURE__ */ C("div", {
|
|
174
|
+
className: "absolute inset-0 flex items-center justify-center",
|
|
175
|
+
children: /* @__PURE__ */ C("span", {
|
|
176
|
+
className: "inline-flex h-11 w-11 items-center justify-center rounded-full bg-bg-surface/85 shadow-lg backdrop-blur transition-transform duration-200 group-hover:scale-110",
|
|
177
|
+
children: /* @__PURE__ */ C(S, { className: "ml-0.5 h-5 w-5 fill-current text-[var(--color-accent-primary)]" })
|
|
178
|
+
})
|
|
179
|
+
})]
|
|
180
|
+
}), /* @__PURE__ */ w("div", {
|
|
181
|
+
className: "flex min-w-0 flex-1 flex-col gap-2 p-4",
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ C("p", {
|
|
184
|
+
className: "line-clamp-1 font-semibold text-text-primary",
|
|
185
|
+
children: n
|
|
186
|
+
}),
|
|
187
|
+
t?.summary && /* @__PURE__ */ C("p", {
|
|
188
|
+
className: "line-clamp-2 text-sm text-text-secondary",
|
|
189
|
+
children: t.summary
|
|
190
|
+
}),
|
|
191
|
+
/* @__PURE__ */ w("div", {
|
|
192
|
+
className: "mt-auto space-y-2.5 pt-2",
|
|
193
|
+
children: [/* @__PURE__ */ w("div", {
|
|
194
|
+
className: "flex items-center gap-2",
|
|
195
|
+
children: [/* @__PURE__ */ C(l, {
|
|
196
|
+
value: e.progressPct,
|
|
197
|
+
className: "flex-1"
|
|
198
|
+
}), /* @__PURE__ */ w("span", {
|
|
199
|
+
className: "shrink-0 text-xs font-semibold tabular-nums text-text-secondary",
|
|
200
|
+
children: [e.progressPct, "%"]
|
|
201
|
+
})]
|
|
202
|
+
}), /* @__PURE__ */ w("div", {
|
|
203
|
+
className: "flex items-center justify-between gap-2",
|
|
204
|
+
children: [/* @__PURE__ */ w("span", {
|
|
205
|
+
className: "truncate text-xs text-text-secondary",
|
|
206
|
+
children: ["Active ", a(e.lastActiveAt ?? e.enrolledAt)]
|
|
207
|
+
}), /* @__PURE__ */ w(T, {
|
|
208
|
+
size: "sm",
|
|
209
|
+
className: "min-h-[40px] shrink-0",
|
|
210
|
+
onClick: (e) => r(e),
|
|
211
|
+
children: [/* @__PURE__ */ C(S, { className: "mr-1 h-4 w-4 fill-current" }), " Continue"]
|
|
212
|
+
})]
|
|
213
|
+
})]
|
|
214
|
+
})
|
|
215
|
+
]
|
|
216
|
+
})]
|
|
217
|
+
}, e.id);
|
|
218
|
+
})
|
|
117
219
|
})
|
|
118
220
|
}),
|
|
119
|
-
/* @__PURE__ */
|
|
221
|
+
/* @__PURE__ */ C(c, {
|
|
120
222
|
title: "Explore the catalog",
|
|
121
223
|
description: "Newly published courses in your workspace.",
|
|
122
|
-
action: /* @__PURE__ */
|
|
224
|
+
action: /* @__PURE__ */ w(T, {
|
|
123
225
|
variant: "ghost",
|
|
124
226
|
size: "sm",
|
|
125
|
-
onClick: () =>
|
|
126
|
-
children: ["Browse all ", /* @__PURE__ */
|
|
227
|
+
onClick: () => k("catalog"),
|
|
228
|
+
children: ["Browse all ", /* @__PURE__ */ C(_, { className: "ml-1 h-4 w-4" })]
|
|
127
229
|
}),
|
|
128
|
-
children:
|
|
230
|
+
children: P.length === 0 ? /* @__PURE__ */ C(o, {
|
|
129
231
|
illustration: "empty-data",
|
|
130
232
|
title: "No published courses yet",
|
|
131
233
|
description: "Create and publish a course in Authoring to populate the catalog.",
|
|
132
|
-
action: /* @__PURE__ */
|
|
133
|
-
onClick: () =>
|
|
234
|
+
action: /* @__PURE__ */ C(T, {
|
|
235
|
+
onClick: () => k("authoring"),
|
|
134
236
|
children: "Go to Authoring"
|
|
135
237
|
})
|
|
136
|
-
}) : /* @__PURE__ */
|
|
238
|
+
}) : /* @__PURE__ */ C("div", {
|
|
137
239
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
138
|
-
children:
|
|
240
|
+
children: P.slice(0, 8).map((e) => /* @__PURE__ */ C(h, {
|
|
139
241
|
course: e,
|
|
140
|
-
onClick: () =>
|
|
242
|
+
onClick: () => k(`catalog/${e.id}`)
|
|
141
243
|
}, e.id))
|
|
142
244
|
})
|
|
143
245
|
})
|
|
@@ -146,6 +248,6 @@ var C = () => {
|
|
|
146
248
|
});
|
|
147
249
|
};
|
|
148
250
|
//#endregion
|
|
149
|
-
export {
|
|
251
|
+
export { k as DashboardPage };
|
|
150
252
|
|
|
151
253
|
//# sourceMappingURL=DashboardPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/buildmyiq/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — the Buildmyiq landing surface.\n * @module buildmyiq/pages\n *\n * A learner + educator overview built entirely from real `bui*` data:\n * - KPI row: published courses, my active enrollments, completed, certificates\n * - Continue learning: the caller's in-progress enrollments\n * - Explore: recently published catalog courses\n * Every number and row is derived from live hooks; empty states are honest.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, GraduationCap, CheckCircle2, Award, ArrowRight, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, MetricsCard, EmptyState, LoadingView, Section, MiniBar } from '../components';\nimport { CourseCard } from './_shared';\nimport { useBuildmyiq } from '../context';\nimport { useBuiCourses, useBuiEnrollments, useBuiCertificates } from '../hooks';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { BuiCourse, BuiEnrollment } from '../types';\n\nexport const DashboardPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 50 });\n const enrollmentsQuery = useBuiEnrollments({ learnerId, limit: 50, enabled: Boolean(learnerId) });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const courses: BuiCourse[] = coursesQuery.data?.items ?? [];\n const enrollments: BuiEnrollment[] = enrollmentsQuery.data?.items ?? [];\n const certificates = certsQuery.data ?? [];\n\n const activeEnrollments = useMemo(\n () =>\n enrollments\n .filter((e) => e.status === 'ENROLLED' || e.status === 'IN_PROGRESS')\n .sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n ),\n [enrollments]\n );\n const completedCount = enrollments.filter((e) => e.status === 'COMPLETED').length;\n\n const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'there';\n\n const isLoading = coursesQuery.isLoading || (Boolean(learnerId) && enrollmentsQuery.isLoading);\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 return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your learning, teaching and credentialing — all in one workspace.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Loading your learning dashboard…\" />\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={`${courses.length}${coursesQuery.data?.pageInfo?.hasMore ? '+' : ''}`}\n label=\"Published courses\"\n featured\n />\n <MetricsCard\n icon={<GraduationCap className=\"h-5 w-5\" />}\n value={activeEnrollments.length}\n label=\"Courses in progress\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={completedCount}\n label=\"Completed\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={certificates.length}\n label=\"Certificates earned\"\n />\n </div>\n\n {/* Continue learning */}\n <Section\n title=\"Continue learning\"\n description=\"Pick up where you left off.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n My learning <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {activeEnrollments.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No active courses 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 <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {activeEnrollments.slice(0, 4).map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`learning/${e.id}`)}\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 <p className=\"truncate font-medium text-text-primary\">\n {courseTitleById.get(e.courseId) ?? 'Course'}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Active {formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}\n </p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Explore catalog */}\n <Section\n title=\"Explore the catalog\"\n description=\"Newly published courses in your workspace.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('catalog')}>\n Browse all <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {courses.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published courses yet\"\n description=\"Create and publish a course in Authoring to populate the catalog.\"\n action={<Button onClick={() => navigateTo('authoring')}>Go to Authoring</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {courses.slice(0, 8).map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onClick={() => navigateTo(`catalog/${course.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,IAAa,UAA0B;CACrC,IAAM,EAAE,gBAAa,kBAAe,GAAc,EAC5C,IAAY,GAAa,IAEzB,IAAe,EAAc;EAAE,QAAQ;EAAa,OAAO;EAAI,CAAC,EAChE,IAAmB,EAAkB;EAAE;EAAW,OAAO;EAAI,SAAS,EAAQ;EAAY,CAAC,EAC3F,IAAa,EAAmB,KAAA,GAAW,EAAU,EAErD,IAAuB,EAAa,MAAM,SAAS,EAAE,EACrD,IAA+B,EAAiB,MAAM,SAAS,EAAE,EACjE,IAAe,EAAW,QAAQ,EAAE,EAEpC,IAAoB,QAEtB,EACG,QAAQ,MAAM,EAAE,WAAW,cAAc,EAAE,WAAW,cAAc,CACpE,MAAM,GAAG,OACP,EAAE,gBAAgB,EAAE,YAAY,cAAc,EAAE,gBAAgB,EAAE,WAAW,CAC/E,EACL,CAAC,EAAY,CACd,EACK,IAAiB,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAErE,IAAY,GAAa,aAAa,GAAa,MAAM,MAAM,IAAI,CAAC,MAAM,SAE1E,IAAY,EAAa,aAAc,EAAQ,KAAc,EAAiB,WAE9E,IAAkB,QAAc;EACpC,IAAM,oBAAM,IAAI,KAAqB;AACrC,OAAK,IAAM,KAAK,EAAS,GAAI,IAAI,EAAE,IAAI,EAAE,MAAM;AAC/C,SAAO;IACN,CAAC,EAAQ,CAAC;AAEb,QACE,kBAAC,GAAD;EACE,OAAO,iBAAiB;EACxB,aAAY;EACZ,eAAA;EACA,QAAA;YAEC,IACC,kBAAC,GAAD,EAAa,OAAM,oCAAqC,CAAA,GAExD,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAO,GAAG,EAAQ,SAAS,EAAa,MAAM,UAAU,UAAU,MAAM;OACxE,OAAM;OACN,UAAA;OACA,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;OAC3C,OAAO,EAAkB;OACzB,OAAM;OACN,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;OAC1C,OAAO;OACP,OAAM;OACN,QAAO;OACP,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACnC,OAAO,EAAa;OACpB,OAAM;OACN,CAAA;MACE;;IAGN,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,EAAW,WAAW;gBAAvE,CAAyE,gBAC3D,kBAAC,GAAD,EAAY,WAAU,gBAAiB,CAAA,CAC5C;;eAGV,EAAkB,WAAW,IAC5B,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QAAQ,kBAAC,GAAD;OAAQ,eAAe,EAAW,UAAU;iBAAE;OAA4B,CAAA;MAClF,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAkB,MAAM,GAAG,EAAE,CAAC,KAAK,MAClC,kBAAC,GAAD;OAEE,WAAU;OACV,WAAU;OACV,eAAe,EAAW,YAAY,EAAE,KAAK;iBAJ/C,CAME,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAY,WAAU,6BAA8B,CAAA;QAChD,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAgB,IAAI,EAAE,SAAS,IAAI;UAClC,CAAA;SACJ,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAS,OAAO,EAAE;WAAa,WAAU;WAAW,CAAA,EACpD,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACG,EAAE,aAAY,IACV;aACH;;SACN,kBAAC,KAAD;UAAG,WAAU;oBAAb,CAAgD,WACtC,EAAmB,EAAE,gBAAgB,EAAE,WAAW,CACxD;;SACA;UACI;SAtBL,EAAE,GAsBG,CACZ;MACE,CAAA;KAEA,CAAA;IAGV,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,EAAW,UAAU;gBAAtE,CAAwE,eAC3D,kBAAC,GAAD,EAAY,WAAU,gBAAiB,CAAA,CAC3C;;eAGV,EAAQ,WAAW,IAClB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QAAQ,kBAAC,GAAD;OAAQ,eAAe,EAAW,YAAY;iBAAE;OAAwB,CAAA;MAChF,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,MACxB,kBAAC,GAAD;OAEU;OACR,eAAe,EAAW,WAAW,EAAO,KAAK;OACjD,EAHK,EAAO,GAGZ,CACF;MACE,CAAA;KAEA,CAAA;IACN;;EAEG,CAAA"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/buildmyiq/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — the Buildmyiq landing surface.\n * @module buildmyiq/pages\n *\n * A learner + educator overview built entirely from real `bui*` data:\n * - KPI row: published courses, my active enrollments, completed, certificates\n * — each stat card carries a tinted icon tile and (where derivable) a\n * Sparkline trend built from the REAL timestamps of the underlying rows.\n * - Continue learning: the caller's in-progress enrollments as rich cards with\n * real cover art, summary, a progress bar and a Continue action.\n * - Explore: recently published catalog courses.\n * Every number, row, avatar and trend is derived from live hooks; empty states\n * are honest and no value is fabricated.\n */\n\nimport type { FC, ReactNode, MouseEvent } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, Clock, CheckCircle2, Award, ArrowRight, Play } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n EmptyState,\n LoadingView,\n Section,\n MiniBar,\n Avatar,\n Sparkline,\n} from '../components';\nimport { CourseCard } from './_shared';\nimport { cn } from '../utils/cn';\nimport { useBuildmyiq } from '../context';\nimport { useBuiCourses, useBuiEnrollments, useBuiCertificates } from '../hooks';\nimport { formatRelativeTime, formatEnumLabel } from '../utils/formatters';\nimport type { BuiCourse, BuiEnrollment } from '../types';\n\n/**\n * Build a cumulative-count trend series from a set of REAL event timestamps\n * (e.g. `publishedAt`, `enrolledAt`, `issuedAt`). The span from the earliest\n * event to now is split into `buckets` slots and each slot holds the running\n * count of events up to its boundary — a genuine, non-fabricated growth line.\n * Returns fewer than two points when there is not enough history to plot; the\n * caller then omits the sparkline rather than drawing a meaningless flat line.\n */\nconst cumulativeSeries = (\n isoDates: Array<string | null | undefined>,\n buckets = 8\n): number[] => {\n const times = isoDates\n .map((d) => (d ? new Date(d).getTime() : Number.NaN))\n .filter((t) => !Number.isNaN(t))\n .sort((a, b) => a - b);\n if (times.length < 2) return times.length === 1 ? [1] : [];\n const start = times[0];\n const end = Date.now();\n const span = Math.max(1, end - start);\n const series: number[] = [];\n for (let i = 1; i <= buckets; i += 1) {\n const boundary = start + (span * i) / buckets;\n series.push(times.filter((t) => t <= boundary).length);\n }\n return series;\n};\n\n/** A tinted, rounded icon tile used in the KPI stat cards. */\nconst StatIcon: FC<{ tint: string; children: ReactNode }> = ({ tint, children }) => (\n <span\n className={cn('inline-flex h-10 w-10 items-center justify-center rounded-xl', tint)}\n aria-hidden=\"true\"\n >\n {children}\n </span>\n);\n\nexport const DashboardPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 50 });\n const enrollmentsQuery = useBuiEnrollments({ learnerId, limit: 50, enabled: Boolean(learnerId) });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const courses: BuiCourse[] = coursesQuery.data?.items ?? [];\n const enrollments: BuiEnrollment[] = enrollmentsQuery.data?.items ?? [];\n const certificates = certsQuery.data ?? [];\n\n const activeEnrollments = useMemo(\n () =>\n enrollments\n .filter((e) => e.status === 'ENROLLED' || e.status === 'IN_PROGRESS')\n .sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n ),\n [enrollments]\n );\n const completedCount = enrollments.filter((e) => e.status === 'COMPLETED').length;\n\n const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'there';\n const fullName = [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ').trim();\n const displayName = currentUser?.name || fullName || (firstName === 'there' ? '' : firstName);\n const roleLabel = currentUser?.role ? formatEnumLabel(currentUser.role) : currentUser?.email;\n\n const isLoading = coursesQuery.isLoading || (Boolean(learnerId) && enrollmentsQuery.isLoading);\n\n const courseById = useMemo(() => {\n const map = new Map<string, BuiCourse>();\n for (const c of courses) map.set(c.id, c);\n return map;\n }, [courses]);\n\n // REAL trend lines — cumulative growth from each entity's own timestamps.\n const trends = useMemo(\n () => ({\n published: cumulativeSeries(courses.map((c) => c.publishedAt ?? c.createdAt)),\n inProgress: cumulativeSeries(enrollments.map((e) => e.enrolledAt)),\n completed: cumulativeSeries(\n enrollments\n .filter((e) => e.status === 'COMPLETED')\n .map((e) => e.completedAt ?? e.updatedAt)\n ),\n certificates: cumulativeSeries(certificates.map((c) => c.issuedAt)),\n }),\n [courses, enrollments, certificates]\n );\n\n const kpis: Array<{\n key: string;\n icon: ReactNode;\n tint: string;\n value: string | number;\n label: string;\n featured?: boolean;\n series: number[];\n sparkColor: string;\n }> = [\n {\n key: 'published',\n icon: <BookOpen className=\"h-5 w-5\" />,\n tint: 'bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]',\n value: `${courses.length}${coursesQuery.data?.pageInfo?.hasMore ? '+' : ''}`,\n label: 'Published courses',\n featured: true,\n series: trends.published,\n sparkColor: 'var(--color-accent-primary)',\n },\n {\n key: 'in-progress',\n icon: <Clock className=\"h-5 w-5\" />,\n tint: 'bg-[color-mix(in_srgb,var(--color-accent-teal)_16%,transparent)] text-[var(--color-accent-teal)]',\n value: activeEnrollments.length,\n label: 'Courses in progress',\n series: trends.inProgress,\n sparkColor: 'var(--color-accent-teal)',\n },\n {\n key: 'completed',\n icon: <CheckCircle2 className=\"h-5 w-5\" />,\n tint: 'bg-status-success-bg-subtle text-status-success-text',\n value: completedCount,\n label: 'Completed',\n series: trends.completed,\n sparkColor: 'var(--color-status-success-text)',\n },\n {\n key: 'certificates',\n icon: <Award className=\"h-5 w-5\" />,\n tint: 'bg-status-warning-bg-subtle text-status-warning-text',\n value: certificates.length,\n label: 'Certificates earned',\n series: trends.certificates,\n sparkColor: 'var(--color-status-warning-text)',\n },\n ];\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your learning, teaching and credentialing — all in one workspace.\"\n titleGradient\n aurora\n actions={\n currentUser ? (\n <div className=\"flex w-fit items-center gap-2.5 rounded-full border border-border-seam bg-bg-surface/60 py-1.5 pl-1.5 pr-3.5 backdrop-blur\">\n <Avatar name={displayName || null} seed={currentUser.id} src={currentUser.avatar} size=\"sm\" />\n {(displayName || roleLabel) && (\n <div className=\"min-w-0 leading-tight\">\n {displayName && (\n <p className=\"truncate text-sm font-medium text-text-primary\">{displayName}</p>\n )}\n {roleLabel && (\n <p className=\"max-w-[10rem] truncate text-xs text-text-secondary\">{roleLabel}</p>\n )}\n </div>\n )}\n </div>\n ) : undefined\n }\n >\n {isLoading ? (\n <LoadingView label=\"Loading your learning dashboard…\" />\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 {kpis.map((k) => (\n <MetricsCard\n key={k.key}\n icon={<StatIcon tint={k.tint}>{k.icon}</StatIcon>}\n value={k.value}\n label={k.label}\n featured={k.featured}\n pill={\n k.series.length >= 2 ? (\n <Sparkline data={k.series} color={k.sparkColor} />\n ) : undefined\n }\n />\n ))}\n </div>\n\n {/* Continue learning */}\n <Section\n title=\"Continue learning\"\n description=\"Pick up where you left off.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n My learning <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {activeEnrollments.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No active courses 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 <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {activeEnrollments.slice(0, 4).map((e) => {\n const course = courseById.get(e.courseId);\n const title = course?.title ?? 'Course';\n const goToCourse = (ev?: MouseEvent) => {\n ev?.stopPropagation();\n navigateTo(`learning/${e.id}`);\n };\n return (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-shadow hover:shadow-lg sm:flex-row\"\n onClick={() => goToCourse()}\n >\n {/* Cover / play thumbnail */}\n <div className=\"relative h-36 w-full shrink-0 overflow-hidden bg-bg-sunken sm:h-auto sm:w-40\">\n {course?.cover?.downloadUrl ? (\n <img\n src={course.cover.downloadUrl}\n alt=\"\"\n className=\"h-full w-full object-cover transition-transform duration-500 group-hover:scale-105\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-[var(--color-accent-soft)] to-bg-sunken\">\n <BookOpen className=\"h-8 w-8 text-text-secondary opacity-60\" />\n </div>\n )}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-bg-surface/85 shadow-lg backdrop-blur transition-transform duration-200 group-hover:scale-110\">\n <Play className=\"ml-0.5 h-5 w-5 fill-current text-[var(--color-accent-primary)]\" />\n </span>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2 p-4\">\n <p className=\"line-clamp-1 font-semibold text-text-primary\">{title}</p>\n {course?.summary && (\n <p className=\"line-clamp-2 text-sm text-text-secondary\">\n {course.summary}\n </p>\n )}\n <div className=\"mt-auto space-y-2.5 pt-2\">\n <div className=\"flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"flex-1\" />\n <span className=\"shrink-0 text-xs font-semibold tabular-nums text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate text-xs text-text-secondary\">\n Active {formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}\n </span>\n <Button\n size=\"sm\"\n className=\"min-h-[40px] shrink-0\"\n onClick={(ev: MouseEvent) => goToCourse(ev)}\n >\n <Play className=\"mr-1 h-4 w-4 fill-current\" /> Continue\n </Button>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Explore catalog */}\n <Section\n title=\"Explore the catalog\"\n description=\"Newly published courses in your workspace.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('catalog')}>\n Browse all <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {courses.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published courses yet\"\n description=\"Create and publish a course in Authoring to populate the catalog.\"\n action={<Button onClick={() => navigateTo('authoring')}>Go to Authoring</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {courses.slice(0, 8).map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onClick={() => navigateTo(`catalog/${course.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssessmentDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAqB,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"AssessmentDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAgHnE,eAAO,MAAM,oBAAoB,EAAE,EA4blC,CAAC"}
|