@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 +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, 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=\"Assessment results\"\n description=\"Your submitted assessments, scores and educator feedback.\"\n >\n {renderResults()}\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":";;;;;;;;;;;;;;;;;;AAuCA,IAAM,IAAuB,IAAI,IAAI,CAAC,YAAY,cAAc,CAAC,EAEpD,UAA2B;CACtC,IAAM,EAAE,gBAAa,kBAAe,GAAc,EAC5C,IAAY,GAAa,IAEzB,CAAC,GAAK,KAAU,EAAsB,cAAc,EAEpD,IAAmB,EAAkB;EACzC;EACA,OAAO;EACP,SAAS,EAAQ;EAClB,CAAC,EACI,IAAe,EAAc,EAAE,OAAO,KAAK,CAAC,EAC5C,IAAa,EAAmB,KAAA,GAAW,EAAU,EACrD,IAAmB,EAAkB;EACzC;EACA,OAAO;EACP,SAAS,EAAQ;EAClB,CAAC,EACI,IAAc,QAEhB,CAAC,GAAI,EAAiB,MAAM,SAAS,EAAE,CAAE,CAAC,MAAM,GAAG,OAChD,EAAE,eAAe,EAAE,WAAW,cAAc,EAAE,eAAe,EAAE,UAAU,CAC3E,EACH,CAAC,EAAiB,KAAK,CACxB,EAEK,IAA+B,QAC7B,EAAiB,MAAM,SAAS,EAAE,EACxC,CAAC,EAAiB,KAAK,CACxB,EACK,IAAuB,QAAc,EAAa,MAAM,SAAS,EAAE,EAAE,CAAC,EAAa,KAAK,CAAC,EACzF,IAAiC,QAAc,EAAW,QAAQ,EAAE,EAAE,CAAC,EAAW,KAAK,CAAC,EAExF,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,EAEP,KAAY,MAA6B,EAAgB,IAAI,EAAS,IAAI,UAE1E,IAAa,QACX,EAAY,QAAQ,MAAM,EAAqB,IAAI,EAAE,OAAO,CAAC,EACnE,CAAC,EAAY,CACd,EACK,IAAY,QACV,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,EACzD,CAAC,EAAY,CACd,EAEK,IAAU,QAEP,CAAC,GADK,MAAQ,gBAAgB,IAAa,MAAQ,cAAc,IAAY,EACpE,CAAC,MAAM,GAAG,OACvB,EAAE,gBAAgB,EAAE,YAAY,cAAc,EAAE,gBAAgB,EAAE,WAAW,CAC/E,EACA;EAAC;EAAK;EAAY;EAAW;EAAY,CAAC,EAEvC,IAAqB,QACnB,CAAC,GAAG,EAAa,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,CAAC,EAC5E,CAAC,EAAa,CACf,EAEK,IAA+B;EACnC;GAAE,OAAO;GAAe,OAAO;GAAe,OAAO,EAAW;GAAQ;EACxE;GAAE,OAAO;GAAa,OAAO;GAAa,OAAO,EAAU;GAAQ;EACnE;GAAE,OAAO;GAAO,OAAO;GAAO,OAAO,EAAY;GAAQ;EAC1D,EAEK,IAAY,EAAQ,MAAe,EAAiB,aAAa,EAAa;AAqMpF,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,eAAA;EACA,QAAA;YAEA,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KAAS,OAAM;KAAU,aAAY;eAArC,CACG,KAAa,EAAY,SAAS,KACjC,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAM,OAAO;OAAM,OAAO;OAAK,UAAU;OAAU,CAAA;MAC/C,CAAA,EA9MT,IASD,IAAkB,kBAAC,GAAD,EAAa,OAAM,6BAA8B,CAAA,GACnE,EAAiB,UAEjB,kBAAC,GAAD;MACE,OAAM;MACN,SACE,EAAiB,iBAAiB,QAAQ,EAAiB,MAAM,UAAU,KAAA;MAE7E,eAAe,EAAiB,SAAS;MACzC,CAAA,GAGF,EAAY,WAAW,IAEvB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QAAQ,kBAAC,GAAD;OAAQ,eAAe,EAAW,UAAU;iBAAE;OAA4B,CAAA;MAClF,CAAA,GAGF,EAAQ,WAAW,IAQnB,kBAAC,GAAD;MACE,cAAa;MACb,OARF,MAAQ,gBACJ,2BACA,MAAQ,cACN,6BACA;MAKJ,aACE,MAAQ,cACJ,6DACA;MAEN,CAAA,GAIJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,KAAK,MACZ,kBAAC,GAAD;OAEE,WAAU;OACV,WAAU;iBAHZ;QAKE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAY,WAAU,6BAA8B,CAAA;SAChD,CAAA;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBAA0C,EAAS,EAAE,SAAS;YAAK,CAAA,EAChF,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;UACN,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD;YAAS,OAAO,EAAE;YAAa,WAAU;YAAoB,CAAA,EAC7D,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACG,KAAK,MAAM,EAAE,eAAe,EAAE,EAAC,IAC3B;cACH;;UACN,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAE,WAAW,eAAe,EAAE,cAC3B,aAAa,EAAmB,EAAE,YAAY,KAC9C,UAAU,EAAmB,EAAE,gBAAgB,EAAE,WAAW;WAC9D,CAAA;UACA;;QACN,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,eAAe,EAAW,YAAY,EAAE,KAAK;mBAJ/C,CAMG,EAAE,WAAW,cAAc,WAAW,YACvC,kBAAC,GAAD,EAAY,WAAU,gBAAiB,CAAA,CAChC;;QACC;SAjCL,EAAE,GAiCG,CACZ;MACE,CAAA,GAvFJ,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,CA2MQ;;IAEV,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;eA1Eb,IACD,EAAiB,YAAkB,kBAAC,GAAD,EAAa,OAAM,yBAA0B,CAAA,GAChF,EAAiB,UAEjB,kBAAC,GAAD;MACE,OAAM;MACN,SAAS,EAAiB,iBAAiB,QAAQ,EAAiB,MAAM,UAAU,KAAA;MACpF,eAAe,EAAiB,SAAS;MACzC,CAAA,GAEF,EAAY,WAAW,IAEvB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,GAGJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;OAAsB,WAAU;OAAQ,WAAU;iBAAlD;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAgB,WAAU,6BAA8B,CAAA;SACpD,CAAA;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,KAAD;aAAG,WAAU;uBAAb,CAA6C,YAAS,EAAE,QAAY;;YACpE,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;YAChC,EAAE,UAAU,QACX,kBAAC,QAAD;aACE,WAAW,uBAAuB,EAAE,SAAS,6BAA6B;uBAEzE,EAAE,SAAS,WAAW;aAClB,CAAA;YAEL;;UACL,EAAE,YAAY,kBAAC,KAAD;WAAG,WAAU;qBAAoC,EAAE;WAAa,CAAA;UAC/E,kBAAC,KAAD;WAAG,WAAU;qBAAb;YAAgD;YACnC,EAAW,EAAE,eAAe,EAAE,UAAU;YAClD,EAAE,WAAW,aAAa,EAAW,EAAE,SAAS,KAAK;YACpD;;UACA;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAE,SAAS,OAAuB,MAAhB,GAAG,EAAE,MAAM;UAC5B,CAAA,EACJ,kBAAC,KAAD;UAAG,WAAU;oBAA8B;UAAS,CAAA,CAChD;;QACI;SA5BI,EAAE,GA4BN,CACZ;MACE,CAAA,GAnDe;KA6ET,CAAA;IAEV,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,EAAW,eAAe;gBAA3E,CACE,kBAAC,GAAD,EAAe,WAAU,gBAAiB,CAAA,EAAA,oBACnC;;eAlIZ,IACD,EAAW,YAAkB,kBAAC,GAAD,EAAa,OAAM,8BAA+B,CAAA,GAC/E,EAAW,UAEX,kBAAC,GAAD;MACE,OAAM;MACN,SAAS,EAAW,iBAAiB,QAAQ,EAAW,MAAM,UAAU,KAAA;MACxE,eAAe,EAAW,SAAS;MACnC,CAAA,GAGF,EAAmB,WAAW,IAE9B,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,GAIJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAmB,KAAK,MACvB,kBAAC,GAAD;OAAyB,WAAU;OAAQ,WAAU;iBAArD;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAO,WAAU,6BAA8B,CAAA;UAC3C,CAAA,EACN,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAA0C,EAAS,EAAK,SAAS;UAAK,CAAA,EACnF,kBAAC,KAAD;UAAG,WAAU;oBAAgD,EAAK;UAAW,CAAA,CACzE;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD,EAAA,UAAA,CAAM,WAAQ,EAAW,EAAK,SAAS,CAAQ,EAAA,CAAA,EAC9C,EAAK,OAAO,kBAAC,GAAD,EAAU,QAAQ,EAAK,KAAK,IAAM,CAAA,GAAG,KAC9C;;QACI;SAfI,EAAK,GAeT,CACZ;MACE,CAAA,GAxCe;KAsIT,CAAA;IACN;;EACK,CAAA"}
|
|
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, their assessment results\n * (`buiListSubmissions`), and the credentials they have earned\n * (`buiListCertificates`). Course titles + covers are resolved from\n * `buiListCourses` (id → course map, falling back to \"Course\"). Every list\n * carries an honest loading / error / empty state — no fabricated rows.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport {\n GraduationCap,\n Award,\n ArrowRight,\n PlayCircle,\n ClipboardCheck,\n CheckCircle2,\n XCircle,\n Users,\n Calendar,\n TrendingUp,\n} 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 DataTable,\n ProgressRing,\n Sparkline,\n} from '../../components';\nimport type { TabItem, Column } from '../../components';\nimport { cn } from '../../utils/cn';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiEnrollments,\n useBuiCourses,\n useBuiCertificates,\n useBuiSubmissions,\n} from '../../hooks';\nimport { formatRelativeTime, formatDate, formatDateTime } from '../../utils/formatters';\nimport type { BuiEnrollment, BuiCourse, BuiCertificate, BuiSubmission } from '../../types';\n\ntype LearningTab = 'in_progress' | 'completed' | 'all';\n\nconst IN_PROGRESS_STATUSES = new Set(['ENROLLED', 'IN_PROGRESS']);\n\nconst scoreClass = (s: BuiSubmission): string =>\n s.passed == null\n ? 'text-text-primary'\n : s.passed\n ? 'text-status-success-text'\n : 'text-status-error-text';\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 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 const enrollmentById = useMemo(() => {\n const map = new Map<string, BuiEnrollment>();\n for (const e of enrollments) map.set(e.id, e);\n return map;\n }, [enrollments]);\n\n const titleFor = (courseId: string): string => courseById.get(courseId)?.title ?? 'Course';\n\n /** Resolve a submission's course via its enrollment (falls back honestly). */\n const courseTitleForSubmission = (s: BuiSubmission): string => {\n const enr = s.enrollmentId ? enrollmentById.get(s.enrollmentId) : undefined;\n return enr ? titleFor(enr.courseId) : 'Assessment';\n };\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 // Derived score summary (real graded submissions only) — powers the trend strip.\n const graded = useMemo(() => submissions.filter((s) => s.score != null), [submissions]);\n const scoreTrend = useMemo(\n () => [...graded].reverse().map((s) => s.score as number),\n [graded]\n );\n const avgScore =\n graded.length > 0\n ? Math.round(graded.reduce((sum, s) => sum + (s.score ?? 0), 0) / graded.length)\n : null;\n const passedCount = useMemo(() => submissions.filter((s) => s.passed === true).length, [submissions]);\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=\"grid grid-cols-1 gap-4 xl:grid-cols-2\">\n {visible.map((e) => {\n const course = courseById.get(e.courseId);\n const cover = course?.cover?.downloadUrl ?? null;\n const isCompleted = e.status === 'COMPLETED';\n const pct = Math.round(e.progressPct ?? 0);\n return (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"group flex flex-col overflow-hidden transition-shadow hover:shadow-lg sm:flex-row\"\n >\n {/* Media tile — real cover when available, soft accent fallback; play\n affordance + a live progress ring badge. */}\n <div className=\"relative aspect-video w-full shrink-0 overflow-hidden bg-[var(--color-accent-soft)] sm:aspect-auto sm:w-44\">\n {cover ? (\n <img\n src={cover}\n alt=\"\"\n className=\"h-full w-full object-cover transition-transform duration-500 group-hover:scale-105\"\n />\n ) : null}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"flex h-11 w-11 items-center justify-center rounded-full bg-bg-surface/85 text-text-primary shadow-sm backdrop-blur transition-transform group-hover:scale-110\">\n <PlayCircle className=\"h-6 w-6\" />\n </span>\n </div>\n <div className=\"absolute bottom-2 left-2 rounded-full bg-bg-surface/85 p-0.5 shadow-sm backdrop-blur\">\n <ProgressRing\n value={pct}\n size={38}\n stroke={5}\n color={\n isCompleted\n ? 'var(--color-status-success-text)'\n : 'var(--color-accent-teal)'\n }\n />\n </div>\n </div>\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2 p-4\">\n <div className=\"flex flex-wrap items-start gap-2\">\n <p className=\"min-w-0 flex-1 truncate font-semibold text-text-primary\">\n {titleFor(e.courseId)}\n </p>\n <StatusBadge status={e.status} />\n </div>\n\n {(course?.level || course?.skill?.name || e.cohort?.name) && (\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n {course?.level && <StatusBadge status={course.level} />}\n {course?.skill?.name && (\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {course.skill.name}\n </span>\n )}\n {e.cohort?.name && (\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" /> {e.cohort.name}\n </span>\n )}\n </div>\n )}\n\n <div className=\"mt-1 flex items-center gap-2\">\n <MiniBar\n value={e.progressPct}\n barClassName={\n isCompleted\n ? 'bg-[var(--color-status-success-text)]'\n : 'bg-[var(--color-accent-primary)]'\n }\n className=\"flex-1\"\n />\n <span className=\"shrink-0 text-xs font-semibold tabular-nums text-text-secondary\">\n {pct}%\n </span>\n </div>\n\n <p className=\"text-xs text-text-secondary\">\n {isCompleted && e.completedAt\n ? `Completed ${formatRelativeTime(e.completedAt)}`\n : `Active ${formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}`}\n </p>\n\n <div className=\"mt-auto flex flex-wrap gap-2 pt-2\">\n <Button\n size=\"sm\"\n className=\"max-sm:flex-1\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n {isCompleted ? 'Review' : 'Continue'}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"max-sm:flex-1\"\n onClick={() => navigateTo(`catalog/${e.courseId}`)}\n >\n Overview\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n );\n };\n\n const resultColumns: Column<BuiSubmission>[] = [\n {\n key: 'assessment',\n header: 'Assessment',\n render: (s) => (\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <ClipboardCheck className=\"h-4 w-4 text-text-primary\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">\n {courseTitleForSubmission(s)}\n </p>\n <p className=\"text-xs text-text-secondary\">Attempt {s.attempt}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'result',\n header: 'Result',\n render: (s) =>\n s.passed == null ? (\n <span className=\"text-text-tertiary\">—</span>\n ) : (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 text-sm font-medium',\n s.passed ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {s.passed ? (\n <CheckCircle2 className=\"h-4 w-4\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {s.passed ? 'Passed' : 'Not passed'}\n </span>\n ),\n },\n {\n key: 'feedback',\n header: 'Feedback',\n hideOnMobile: true,\n render: (s) =>\n s.feedback ? (\n <p className=\"line-clamp-2 max-w-[18rem] text-sm text-text-secondary\">{s.feedback}</p>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'submitted',\n header: 'Submitted',\n hideOnMobile: true,\n sortValue: (s) => s.submittedAt ?? s.createdAt,\n render: (s) => (\n <span className=\"whitespace-nowrap text-sm text-text-secondary\">\n {formatDateTime(s.submittedAt ?? s.createdAt)}\n </span>\n ),\n },\n {\n key: 'score',\n header: 'Score',\n align: 'right',\n sortValue: (s) => s.score ?? -1,\n render: (s) => (\n <span className={cn('text-lg font-bold tabular-nums', scoreClass(s))}>\n {s.score != null ? `${s.score}%` : '—'}\n </span>\n ),\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={\n submissionsQuery.error instanceof Error ? submissionsQuery.error.message : undefined\n }\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 {avgScore != null && (\n <GlassCard\n treatment=\"metric\"\n className=\"flex flex-wrap items-center justify-between gap-4 p-4\"\n >\n <div className=\"flex items-center gap-3\">\n <ProgressRing value={avgScore} size={54} />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">Average score</p>\n <p className=\"text-xs text-text-secondary\">\n {passedCount} of {graded.length} passed · {graded.length} graded\n </p>\n </div>\n </div>\n {scoreTrend.length >= 2 && (\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <TrendingUp className=\"h-4 w-4 text-status-success-text\" />\n <Sparkline data={scoreTrend} width={128} height={36} />\n <span>score trend</span>\n </div>\n )}\n </GlassCard>\n )}\n <DataTable\n columns={resultColumns}\n rows={submissions}\n rowKey={(s) => s.id}\n defaultSort={{ key: 'submitted', dir: 'desc' }}\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\n key={cert.id}\n treatment=\"plain\"\n className=\"flex flex-col gap-3 p-4 transition-shadow hover:shadow-lg\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[color-mix(in_srgb,var(--color-status-warning-text)_16%,transparent)]\">\n <Award className=\"h-6 w-6 text-status-warning-text\" />\n </div>\n <StatusBadge status={cert.status} />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{titleFor(cert.courseId)}</p>\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"font-mono\">{cert.serial}</span>\n {cert.finalScore != null && (\n <span className=\"inline-flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> {cert.finalScore}%\n </span>\n )}\n </div>\n </div>\n <div className=\"mt-auto flex flex-wrap items-center justify-between gap-2 border-t border-border-seam/60 pt-3 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Calendar className=\"h-3.5 w-3.5\" /> Issued {formatDate(cert.issuedAt)}\n </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=\"Assessment results\"\n description=\"Your submitted assessments, scores and educator feedback.\"\n >\n {renderResults()}\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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PathsPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/paths/PathsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAuChC,eAAO,MAAM,SAAS,EAAE,EA4LvB,CAAC"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useBuildmyiq as e } from "../../context/BuildmyiqContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { formatHours as r } from "../../utils/formatters.js";
|
|
5
|
+
import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
6
|
+
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
7
|
+
import { ErrorView as o, LoadingView as s } from "../../components/StateViews.js";
|
|
8
|
+
import { ProgressRing as c } from "../../components/Viz.js";
|
|
9
|
+
import "../../context/index.js";
|
|
10
|
+
import { useBuiCourses as l, useBuiEnrollments as u, useBuiSkills as d } from "../../hooks/useBuildmyiqApi.js";
|
|
11
|
+
import "../../hooks/index.js";
|
|
12
|
+
import "../../components/index.js";
|
|
13
|
+
import { useMemo as f } from "react";
|
|
14
|
+
import { ArrowRight as p, CheckCircle2 as m, Circle as h, Clock as g, Layers as _, PlayCircle as v } from "lucide-react";
|
|
15
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
16
|
+
import { Button as x, GlassCard as S } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
//#region src/buildmyiq/pages/paths/PathsPage.tsx
|
|
18
|
+
var C = {
|
|
19
|
+
BEGINNER: 0,
|
|
20
|
+
INTERMEDIATE: 1,
|
|
21
|
+
ADVANCED: 2,
|
|
22
|
+
EXPERT: 3
|
|
23
|
+
}, w = () => {
|
|
24
|
+
let { currentUser: w, navigateTo: T } = e(), E = w?.id, D = d(), O = l({
|
|
25
|
+
status: "PUBLISHED",
|
|
26
|
+
limit: 100
|
|
27
|
+
}), k = u({
|
|
28
|
+
learnerId: E,
|
|
29
|
+
limit: 100,
|
|
30
|
+
enabled: !!E
|
|
31
|
+
}), A = D.data ?? [], j = f(() => O.data?.items ?? [], [O.data]), M = f(() => k.data?.items ?? [], [k.data]), N = f(() => {
|
|
32
|
+
let e = /* @__PURE__ */ new Map();
|
|
33
|
+
for (let t of M) {
|
|
34
|
+
let n = e.get(t.courseId);
|
|
35
|
+
(!n || t.enrolledAt > n.enrolledAt) && e.set(t.courseId, t);
|
|
36
|
+
}
|
|
37
|
+
let t = /* @__PURE__ */ new Map();
|
|
38
|
+
for (let e of j) {
|
|
39
|
+
if (!e.skillId) continue;
|
|
40
|
+
let n = t.get(e.skillId) ?? [];
|
|
41
|
+
n.push(e), t.set(e.skillId, n);
|
|
42
|
+
}
|
|
43
|
+
let n = [];
|
|
44
|
+
for (let r of A) {
|
|
45
|
+
let i = t.get(r.id);
|
|
46
|
+
if (!i || i.length === 0) continue;
|
|
47
|
+
let a = [...i].sort((e, t) => (C[e.level] ?? 9) - (C[t.level] ?? 9) || e.title.localeCompare(t.title)), o = a.map((t) => {
|
|
48
|
+
let n = e.get(t.id);
|
|
49
|
+
return {
|
|
50
|
+
course: t,
|
|
51
|
+
status: n ? n.status === "COMPLETED" ? "completed" : "in_progress" : "not_started",
|
|
52
|
+
progressPct: n?.progressPct ?? 0,
|
|
53
|
+
enrollmentId: n?.id
|
|
54
|
+
};
|
|
55
|
+
}), s = a.reduce((e, t) => e + (t.estimatedHours ?? 0), 0), c = Math.round(o.reduce((e, t) => e + (t.status === "completed" ? 100 : t.progressPct), 0) / (o.length || 1));
|
|
56
|
+
n.push({
|
|
57
|
+
skill: r,
|
|
58
|
+
nodes: o,
|
|
59
|
+
totalHours: s,
|
|
60
|
+
overallPct: c
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return n.sort((e, t) => t.overallPct - e.overallPct || t.nodes.length - e.nodes.length);
|
|
64
|
+
}, [
|
|
65
|
+
A,
|
|
66
|
+
j,
|
|
67
|
+
M
|
|
68
|
+
]), P = D.isLoading || O.isLoading, F = (e) => {
|
|
69
|
+
e.enrollmentId && e.status !== "not_started" ? T(`learning/${e.enrollmentId}`) : T(`catalog/${e.course.id}`);
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ y(n, {
|
|
72
|
+
title: "Skill Journeys",
|
|
73
|
+
description: "Guided, multi-course paths that build a skill to mastery — with your live progress across each step.",
|
|
74
|
+
titleGradient: !0,
|
|
75
|
+
aurora: !0,
|
|
76
|
+
children: P ? /* @__PURE__ */ y(s, { label: "Mapping your skill journeys…" }) : D.isError || O.isError ? /* @__PURE__ */ y(o, {
|
|
77
|
+
title: "Couldn't load journeys",
|
|
78
|
+
onRetry: () => O.refetch()
|
|
79
|
+
}) : N.length === 0 ? /* @__PURE__ */ y(a, {
|
|
80
|
+
illustration: "onboarding",
|
|
81
|
+
title: "No skill journeys yet",
|
|
82
|
+
description: "Journeys appear once published courses are mapped to a skill. Add a skill to a course in Authoring.",
|
|
83
|
+
action: /* @__PURE__ */ y(x, {
|
|
84
|
+
onClick: () => T("catalog"),
|
|
85
|
+
children: "Browse the catalog"
|
|
86
|
+
})
|
|
87
|
+
}) : /* @__PURE__ */ y("div", {
|
|
88
|
+
className: "flex flex-col gap-6",
|
|
89
|
+
children: N.map((e) => /* @__PURE__ */ b(S, {
|
|
90
|
+
treatment: "plain",
|
|
91
|
+
className: "p-5 sm:p-6",
|
|
92
|
+
children: [/* @__PURE__ */ b("div", {
|
|
93
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
94
|
+
children: [/* @__PURE__ */ b("div", {
|
|
95
|
+
className: "flex items-center gap-4",
|
|
96
|
+
children: [/* @__PURE__ */ y(c, {
|
|
97
|
+
value: e.overallPct,
|
|
98
|
+
size: 64,
|
|
99
|
+
color: "var(--color-accent-teal)"
|
|
100
|
+
}), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ b("div", {
|
|
101
|
+
className: "flex flex-wrap items-center gap-2",
|
|
102
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
103
|
+
className: "text-lg font-semibold text-text-primary",
|
|
104
|
+
children: ["Become skilled in ", e.skill.name]
|
|
105
|
+
}), /* @__PURE__ */ y(i, { status: e.skill.category })]
|
|
106
|
+
}), /* @__PURE__ */ b("p", {
|
|
107
|
+
className: "mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm text-text-secondary",
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ b("span", {
|
|
110
|
+
className: "inline-flex items-center gap-1",
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ y(_, { className: "h-3.5 w-3.5" }),
|
|
113
|
+
" ",
|
|
114
|
+
e.nodes.length,
|
|
115
|
+
" courses"
|
|
116
|
+
]
|
|
117
|
+
}),
|
|
118
|
+
e.totalHours > 0 && /* @__PURE__ */ b("span", {
|
|
119
|
+
className: "inline-flex items-center gap-1",
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ y(g, { className: "h-3.5 w-3.5" }),
|
|
122
|
+
" ",
|
|
123
|
+
r(e.totalHours)
|
|
124
|
+
]
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ b("span", { children: [
|
|
127
|
+
"· ",
|
|
128
|
+
e.overallPct,
|
|
129
|
+
"% complete"
|
|
130
|
+
] })
|
|
131
|
+
]
|
|
132
|
+
})] })]
|
|
133
|
+
}), /* @__PURE__ */ b(x, {
|
|
134
|
+
variant: "secondary",
|
|
135
|
+
size: "sm",
|
|
136
|
+
className: "shrink-0 max-sm:w-full",
|
|
137
|
+
onClick: () => {
|
|
138
|
+
let t = e.nodes.find((e) => e.status === "in_progress") ?? e.nodes.find((e) => e.status === "not_started") ?? e.nodes[0];
|
|
139
|
+
t && F(t);
|
|
140
|
+
},
|
|
141
|
+
children: ["Continue path ", /* @__PURE__ */ y(p, { className: "ml-1 h-4 w-4" })]
|
|
142
|
+
})]
|
|
143
|
+
}), /* @__PURE__ */ y("div", {
|
|
144
|
+
className: "mt-5 overflow-x-auto",
|
|
145
|
+
children: /* @__PURE__ */ y("ol", {
|
|
146
|
+
className: "flex min-w-max items-stretch gap-0",
|
|
147
|
+
children: e.nodes.map((n, r) => /* @__PURE__ */ b("li", {
|
|
148
|
+
className: "flex items-stretch",
|
|
149
|
+
children: [/* @__PURE__ */ b("button", {
|
|
150
|
+
type: "button",
|
|
151
|
+
onClick: () => F(n),
|
|
152
|
+
className: t("group flex w-56 flex-col gap-2 rounded-xl border p-3 text-left transition-colors", n.status === "completed" ? "border-[color-mix(in_srgb,var(--color-status-success-text)_40%,transparent)] bg-status-success-bg-subtle" : n.status === "in_progress" ? "border-accent bg-[var(--color-accent-soft)]" : "border-border-seam bg-bg-surface hover:bg-bg-sunken"),
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ b("div", {
|
|
155
|
+
className: "flex items-center justify-between",
|
|
156
|
+
children: [/* @__PURE__ */ b("span", {
|
|
157
|
+
className: "text-xs font-medium text-text-secondary",
|
|
158
|
+
children: ["Step ", r + 1]
|
|
159
|
+
}), n.status === "completed" ? /* @__PURE__ */ y(m, { className: "h-5 w-5 text-status-success-text" }) : n.status === "in_progress" ? /* @__PURE__ */ y(v, { className: "h-5 w-5 text-[var(--color-accent-teal)]" }) : /* @__PURE__ */ y(h, { className: "h-5 w-5 text-[var(--color-text-tertiary)]" })]
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ y("p", {
|
|
162
|
+
className: "line-clamp-2 text-sm font-medium text-text-primary",
|
|
163
|
+
children: n.course.title
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ b("div", {
|
|
166
|
+
className: "mt-auto flex items-center justify-between",
|
|
167
|
+
children: [/* @__PURE__ */ y(i, { status: n.course.level }), n.status === "in_progress" && /* @__PURE__ */ b("span", {
|
|
168
|
+
className: "text-xs tabular-nums text-text-secondary",
|
|
169
|
+
children: [n.progressPct, "%"]
|
|
170
|
+
})]
|
|
171
|
+
})
|
|
172
|
+
]
|
|
173
|
+
}), r < e.nodes.length - 1 && /* @__PURE__ */ y("div", {
|
|
174
|
+
className: "flex w-6 items-center justify-center",
|
|
175
|
+
children: /* @__PURE__ */ y(p, { className: "h-4 w-4 text-[var(--color-text-tertiary)]" })
|
|
176
|
+
})]
|
|
177
|
+
}, n.course.id))
|
|
178
|
+
})
|
|
179
|
+
})]
|
|
180
|
+
}, e.skill.id))
|
|
181
|
+
})
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
export { w as PathsPage };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=PathsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PathsPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/paths/PathsPage.tsx"],"sourcesContent":["/**\n * PathsPage — \"Skill Journeys\": published courses grouped by the skill they\n * build, ordered into a guided progression, with the learner's LIVE progress\n * across each course.\n * @module buildmyiq/pages/paths\n *\n * DERIVED, not a new backend entity: a \"journey\" for a skill = the published\n * courses whose `skillId` is that skill, ordered by level (Beginner → Expert)\n * then title. Each node's state comes from the caller's real enrollment for that\n * course (completed / in-progress with %/ not started). No locking is invented —\n * \"not started\" is honest. No fabricated data.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { CheckCircle2, PlayCircle, Circle, ArrowRight, Clock, Layers } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n LoadingView,\n ErrorView,\n EmptyState,\n StatusBadge,\n ProgressRing,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiSkills, useBuiCourses, useBuiEnrollments } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport { formatHours } from '../../utils/formatters';\nimport type { BuiCourse, BuiEnrollment, BuiSkill } from '../../types';\n\nconst LEVEL_ORDER: Record<string, number> = {\n BEGINNER: 0,\n INTERMEDIATE: 1,\n ADVANCED: 2,\n EXPERT: 3,\n};\n\ninterface JourneyNode {\n course: BuiCourse;\n status: 'completed' | 'in_progress' | 'not_started';\n progressPct: number;\n enrollmentId?: string;\n}\n\ninterface Journey {\n skill: BuiSkill;\n nodes: JourneyNode[];\n totalHours: number;\n overallPct: number;\n}\n\nexport const PathsPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const skillsQuery = useBuiSkills();\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 100 });\n const enrollmentsQuery = useBuiEnrollments({\n learnerId,\n limit: 100,\n enabled: Boolean(learnerId),\n });\n\n const skills = skillsQuery.data ?? [];\n const courses: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const enrollments: BuiEnrollment[] = useMemo(\n () => enrollmentsQuery.data?.items ?? [],\n [enrollmentsQuery.data]\n );\n\n const journeys = useMemo<Journey[]>(() => {\n const enrollByCourse = new Map<string, BuiEnrollment>();\n for (const e of enrollments) {\n // keep the most recent enrollment per course\n const prev = enrollByCourse.get(e.courseId);\n if (!prev || e.enrolledAt > prev.enrolledAt) enrollByCourse.set(e.courseId, e);\n }\n const bySkill = new Map<string, BuiCourse[]>();\n for (const c of courses) {\n if (!c.skillId) continue;\n const list = bySkill.get(c.skillId) ?? [];\n list.push(c);\n bySkill.set(c.skillId, list);\n }\n const result: Journey[] = [];\n for (const skill of skills) {\n const list = bySkill.get(skill.id);\n if (!list || list.length === 0) continue;\n const ordered = [...list].sort(\n (a, b) => (LEVEL_ORDER[a.level] ?? 9) - (LEVEL_ORDER[b.level] ?? 9) || a.title.localeCompare(b.title)\n );\n const nodes: JourneyNode[] = ordered.map((course) => {\n const enr = enrollByCourse.get(course.id);\n const status: JourneyNode['status'] = !enr\n ? 'not_started'\n : enr.status === 'COMPLETED'\n ? 'completed'\n : 'in_progress';\n return {\n course,\n status,\n progressPct: enr?.progressPct ?? 0,\n enrollmentId: enr?.id,\n };\n });\n const totalHours = ordered.reduce((s, c) => s + (c.estimatedHours ?? 0), 0);\n const overallPct = Math.round(\n nodes.reduce((s, n) => s + (n.status === 'completed' ? 100 : n.progressPct), 0) /\n (nodes.length || 1)\n );\n result.push({ skill, nodes, totalHours, overallPct });\n }\n // Journeys with the most progress / courses first.\n return result.sort((a, b) => b.overallPct - a.overallPct || b.nodes.length - a.nodes.length);\n }, [skills, courses, enrollments]);\n\n const isLoading = skillsQuery.isLoading || coursesQuery.isLoading;\n\n const goToCourse = (n: JourneyNode): void => {\n if (n.enrollmentId && n.status !== 'not_started') navigateTo(`learning/${n.enrollmentId}`);\n else navigateTo(`catalog/${n.course.id}`);\n };\n\n return (\n <PageLayout\n title=\"Skill Journeys\"\n description=\"Guided, multi-course paths that build a skill to mastery — with your live progress across each step.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Mapping your skill journeys…\" />\n ) : skillsQuery.isError || coursesQuery.isError ? (\n <ErrorView title=\"Couldn't load journeys\" onRetry={() => coursesQuery.refetch()} />\n ) : journeys.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No skill journeys yet\"\n description=\"Journeys appear once published courses are mapped to a skill. Add a skill to a course in Authoring.\"\n action={<Button onClick={() => navigateTo('catalog')}>Browse the catalog</Button>}\n />\n ) : (\n <div className=\"flex flex-col gap-6\">\n {journeys.map((journey) => (\n <GlassCard key={journey.skill.id} treatment=\"plain\" className=\"p-5 sm:p-6\">\n {/* Journey header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <ProgressRing value={journey.overallPct} size={64} color=\"var(--color-accent-teal)\" />\n <div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n Become skilled in {journey.skill.name}\n </h2>\n <StatusBadge status={journey.skill.category} />\n </div>\n <p className=\"mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <Layers className=\"h-3.5 w-3.5\" /> {journey.nodes.length} courses\n </span>\n {journey.totalHours > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatHours(journey.totalHours)}\n </span>\n )}\n <span>· {journey.overallPct}% complete</span>\n </p>\n </div>\n </div>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"shrink-0 max-sm:w-full\"\n onClick={() => {\n const next =\n journey.nodes.find((n) => n.status === 'in_progress') ??\n journey.nodes.find((n) => n.status === 'not_started') ??\n journey.nodes[0];\n if (next) goToCourse(next);\n }}\n >\n Continue path <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n\n {/* Node stepper — horizontal scroll on small screens */}\n <div className=\"mt-5 overflow-x-auto\">\n <ol className=\"flex min-w-max items-stretch gap-0\">\n {journey.nodes.map((node, i) => (\n <li key={node.course.id} className=\"flex items-stretch\">\n <button\n type=\"button\"\n onClick={() => goToCourse(node)}\n className={cn(\n 'group flex w-56 flex-col gap-2 rounded-xl border p-3 text-left transition-colors',\n node.status === 'completed'\n ? 'border-[color-mix(in_srgb,var(--color-status-success-text)_40%,transparent)] bg-status-success-bg-subtle'\n : node.status === 'in_progress'\n ? 'border-accent bg-[var(--color-accent-soft)]'\n : 'border-border-seam bg-bg-surface hover:bg-bg-sunken'\n )}\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">Step {i + 1}</span>\n {node.status === 'completed' ? (\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text\" />\n ) : node.status === 'in_progress' ? (\n <PlayCircle className=\"h-5 w-5 text-[var(--color-accent-teal)]\" />\n ) : (\n <Circle className=\"h-5 w-5 text-[var(--color-text-tertiary)]\" />\n )}\n </div>\n <p className=\"line-clamp-2 text-sm font-medium text-text-primary\">\n {node.course.title}\n </p>\n <div className=\"mt-auto flex items-center justify-between\">\n <StatusBadge status={node.course.level} />\n {node.status === 'in_progress' && (\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {node.progressPct}%\n </span>\n )}\n </div>\n </button>\n {i < journey.nodes.length - 1 && (\n <div className=\"flex w-6 items-center justify-center\">\n <ArrowRight className=\"h-4 w-4 text-[var(--color-text-tertiary)]\" />\n </div>\n )}\n </li>\n ))}\n </ol>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SkillsPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/skills/SkillsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SkillsPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/skills/SkillsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA4IhC,eAAO,MAAM,UAAU,EAAE,EAyNxB,CAAC"}
|