@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.
Files changed (67) hide show
  1. package/dist/buildmyiq/BuildmyiqRoot.d.ts.map +1 -1
  2. package/dist/buildmyiq/BuildmyiqRoot.js +57 -49
  3. package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
  4. package/dist/buildmyiq/BuildmyiqRoutes.d.ts.map +1 -1
  5. package/dist/buildmyiq/BuildmyiqRoutes.js +15 -11
  6. package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
  7. package/dist/buildmyiq/components/Avatar.d.ts +15 -0
  8. package/dist/buildmyiq/components/Avatar.d.ts.map +1 -0
  9. package/dist/buildmyiq/components/Avatar.js +42 -0
  10. package/dist/buildmyiq/components/Avatar.js.map +1 -0
  11. package/dist/buildmyiq/components/Viz.d.ts +25 -0
  12. package/dist/buildmyiq/components/Viz.d.ts.map +1 -0
  13. package/dist/buildmyiq/components/Viz.js +78 -0
  14. package/dist/buildmyiq/components/Viz.js.map +1 -0
  15. package/dist/buildmyiq/components/index.d.ts +2 -0
  16. package/dist/buildmyiq/components/index.d.ts.map +1 -1
  17. package/dist/buildmyiq/components/index.js +2 -0
  18. package/dist/buildmyiq/pages/DashboardPage.d.ts.map +1 -1
  19. package/dist/buildmyiq/pages/DashboardPage.js +200 -98
  20. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  21. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +1 -1
  22. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js +262 -241
  23. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
  24. package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +1 -1
  25. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js +142 -115
  26. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js.map +1 -1
  27. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts.map +1 -1
  28. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +220 -156
  29. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -1
  30. package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +1 -1
  31. package/dist/buildmyiq/pages/authoring/AuthoringPage.js +51 -46
  32. package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
  33. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -1
  34. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +581 -448
  35. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
  36. package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +1 -1
  37. package/dist/buildmyiq/pages/catalog/CatalogPage.js +52 -42
  38. package/dist/buildmyiq/pages/catalog/CatalogPage.js.map +1 -1
  39. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
  40. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +422 -301
  41. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  42. package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -1
  43. package/dist/buildmyiq/pages/certificates/CertificatesPage.js +237 -151
  44. package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
  45. package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -1
  46. package/dist/buildmyiq/pages/cohorts/CohortsPage.js +219 -138
  47. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
  48. package/dist/buildmyiq/pages/insights/InsightsPage.d.ts.map +1 -1
  49. package/dist/buildmyiq/pages/insights/InsightsPage.js +276 -152
  50. package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -1
  51. package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -1
  52. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +237 -174
  53. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
  54. package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +1 -1
  55. package/dist/buildmyiq/pages/learning/MyLearningPage.js +307 -169
  56. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  57. package/dist/buildmyiq/pages/paths/PathsPage.d.ts +4 -0
  58. package/dist/buildmyiq/pages/paths/PathsPage.d.ts.map +1 -0
  59. package/dist/buildmyiq/pages/paths/PathsPage.js +187 -0
  60. package/dist/buildmyiq/pages/paths/PathsPage.js.map +1 -0
  61. package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +1 -1
  62. package/dist/buildmyiq/pages/skills/SkillsPage.js +233 -140
  63. package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
  64. package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -1
  65. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +379 -275
  66. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
  67. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CertificatesPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/certificates/CertificatesPage.tsx"],"sourcesContent":["/**\n * CertificatesPage — the credentialing surface for the workspace.\n * @module buildmyiq/pages/certificates\n *\n * The primary page of the Certificates section. Lists real `BuiCertificate`\n * rows (issued / expired / revoked) for the workspace, scoped by a \"Mine / All\"\n * learner toggle and an optional course filter. Facilitators can issue a new\n * certificate and revoke an issued one. Every value is derived from live `bui*`\n * data — no fabricated learners, scores, or serials.\n *\n * Reads: `buiListCertificates` (useBuiCertificates), `buiListCourses` (useBuiCourses).\n * Writes: `buiIssueCertificate` (useBuiIssueCertificate),\n * `buiRevokeCertificate` (useBuiRevokeCertificate).\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Plus, Award, Ban, Download } from 'lucide-react';\nimport {\n Button,\n Label,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n DataTable,\n type Column,\n FilterTabs,\n type FilterOption,\n LoadingView,\n ErrorView,\n EmptyState,\n FileLink,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCertificates,\n useBuiCourses,\n useBuiIssueCertificate,\n useBuiRevokeCertificate,\n} from '../../hooks';\nimport { formatDate } from '../../utils/formatters';\nimport type { BuiCertificate } from '../../types';\n\ntype ScopeFilter = 'MINE' | 'ALL';\ntype CourseFilter = string | 'ALL';\n\n/** Convert a native `<input type=\"date\">` value (YYYY-MM-DD) to a DateTime, or undefined. */\nconst toDateTime = (value: string): string | undefined => {\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n const d = new Date(trimmed);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n};\n\n/** A collision-resistant, human-legible default serial for a new credential. */\nconst generateSerial = (): string => `BMIQ-${Date.now().toString(36).toUpperCase()}`;\n\ninterface IssueFormState {\n courseId: string;\n learnerId: string;\n serial: string;\n finalScore: string;\n expiresAt: string;\n}\n\nexport const CertificatesPage: FC = () => {\n const { currentUser } = useBuildmyiq();\n const selfId = currentUser?.id;\n\n const [scope, setScope] = useState<ScopeFilter>(selfId ? 'MINE' : 'ALL');\n const [courseFilter, setCourseFilter] = useState<CourseFilter>('ALL');\n const [dialogOpen, setDialogOpen] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [revokingId, setRevokingId] = useState<string | null>(null);\n\n const makeEmptyForm = (): IssueFormState => ({\n courseId: '',\n learnerId: selfId ?? '',\n serial: generateSerial(),\n finalScore: '',\n expiresAt: '',\n });\n const [form, setForm] = useState<IssueFormState>(makeEmptyForm);\n\n // Course lookup: powers the course filter, the issue-form course picker, and\n // the courseId → title mapping in the table.\n const coursesQuery = useBuiCourses({ limit: 100 });\n const courses = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\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 learnerId = scope === 'MINE' ? selfId : undefined;\n const courseId = courseFilter === 'ALL' ? undefined : courseFilter;\n\n const certsQuery = useBuiCertificates(courseId, learnerId);\n const issueCert = useBuiIssueCertificate();\n const revokeCert = useBuiRevokeCertificate();\n\n const rows = useMemo(() => certsQuery.data ?? [], [certsQuery.data]);\n\n const scopeOptions: FilterOption<ScopeFilter>[] = [\n { value: 'MINE', label: 'Mine' },\n { value: 'ALL', label: 'All' },\n ];\n\n const handleRevoke = async (cert: BuiCertificate): Promise<void> => {\n if (\n typeof window !== 'undefined' &&\n !window.confirm(`Revoke certificate ${cert.serial}? This cannot be undone.`)\n ) {\n return;\n }\n setActionError(null);\n setRevokingId(cert.id);\n try {\n await revokeCert.mutateAsync(cert.id);\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to revoke certificate.');\n } finally {\n setRevokingId(null);\n }\n };\n\n const columns: Column<BuiCertificate>[] = [\n {\n key: 'serial',\n header: 'Serial',\n sortValue: (c) => c.serial.toLowerCase(),\n render: (c) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]\">\n <Award className=\"h-4 w-4\" />\n </div>\n <span className=\"min-w-0 truncate font-mono text-xs font-semibold text-text-primary\">\n {c.serial}\n </span>\n </div>\n ),\n },\n {\n key: 'course',\n header: 'Course',\n sortValue: (c) => (courseTitleById.get(c.courseId) ?? '').toLowerCase(),\n render: (c) => (\n <span className=\"text-text-secondary\">{courseTitleById.get(c.courseId) ?? '—'}</span>\n ),\n },\n {\n key: 'learner',\n header: 'Learner',\n hideOnMobile: true,\n sortValue: (c) => (c.learnerId === selfId ? '' : c.learnerId),\n render: (c) =>\n c.learnerId === selfId ? (\n <span className=\"font-medium text-text-primary\">You</span>\n ) : (\n <span className=\"font-mono text-xs text-text-tertiary\" title={c.learnerId}>\n {c.learnerId.slice(0, 8)}…\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (c) => <StatusBadge status={c.status} />,\n },\n {\n key: 'issuedAt',\n header: 'Issued',\n hideOnMobile: true,\n sortValue: (c) => c.issuedAt,\n render: (c) => <span className=\"text-text-secondary\">{formatDate(c.issuedAt)}</span>,\n },\n {\n key: 'expiresAt',\n header: 'Expires',\n hideOnMobile: true,\n sortValue: (c) => c.expiresAt ?? '',\n render: (c) => <span className=\"text-text-tertiary\">{formatDate(c.expiresAt)}</span>,\n },\n {\n key: 'finalScore',\n header: 'Score',\n align: 'right',\n hideOnMobile: true,\n sortValue: (c) => c.finalScore ?? -1,\n render: (c) => (\n <span className=\"tabular-nums text-text-secondary\">{c.finalScore ?? '—'}</span>\n ),\n },\n {\n key: 'file',\n header: 'File',\n hideOnMobile: true,\n render: (c) =>\n c.file ? <FileLink fileId={c.file.id} /> : <span className=\"text-text-tertiary\">—</span>,\n },\n {\n key: 'actions',\n header: '',\n align: 'right',\n render: (c) =>\n c.status === 'ISSUED' ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-status-error-text hover:text-status-error-text\"\n disabled={revokingId === c.id}\n onClick={() => void handleRevoke(c)}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" />\n {revokingId === c.id ? 'Revoking…' : 'Revoke'}\n </Button>\n ) : null,\n },\n ];\n\n const openDialog = (): void => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = (): void => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const handleIssue = async (): Promise<void> => {\n if (!form.courseId) {\n setFormError('Please choose a course for this certificate.');\n return;\n }\n if (!form.learnerId.trim()) {\n setFormError('A learner (user ID) is required.');\n return;\n }\n if (!form.serial.trim()) {\n setFormError('A certificate serial is required.');\n return;\n }\n const scoreNum = form.finalScore.trim() ? Number(form.finalScore) : undefined;\n if (scoreNum !== undefined && (!Number.isFinite(scoreNum) || scoreNum < 0)) {\n setFormError('Final score must be a non-negative number.');\n return;\n }\n const expiresIso = toDateTime(form.expiresAt);\n\n setFormError(null);\n try {\n await issueCert.mutateAsync({\n courseId: form.courseId,\n learnerId: form.learnerId.trim(),\n serial: form.serial.trim(),\n finalScore: scoreNum,\n expiresAt: expiresIso,\n });\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to issue certificate.');\n }\n };\n\n const hasCourses = courses.length > 0;\n const isFiltered = scope === 'MINE' || courseFilter !== 'ALL';\n\n return (\n <PageLayout\n title=\"Certificates\"\n description=\"Verifiable credentials issued to learners on course completion.\"\n titleGradient\n aurora\n actions={\n <Button onClick={openDialog} disabled={!hasCourses}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Issue certificate\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs options={scopeOptions} value={scope} onChange={setScope} />\n <Select value={courseFilter} onValueChange={(v) => setCourseFilter(v as CourseFilter)}>\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by course\">\n <SelectValue placeholder=\"All courses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All courses</SelectItem>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {certsQuery.isLoading ? (\n <LoadingView label=\"Loading certificates…\" />\n ) : certsQuery.isError ? (\n <ErrorView message=\"Couldn't load certificates.\" onRetry={() => certsQuery.refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration={isFiltered ? 'empty-search' : 'onboarding'}\n icon={<Award className=\"h-10 w-10\" />}\n title={isFiltered ? 'No certificates match these filters' : 'No certificates issued yet'}\n description={\n isFiltered\n ? scope === 'MINE'\n ? \"You haven't earned a certificate for this selection yet.\"\n : 'No certificates have been issued for this course yet.'\n : hasCourses\n ? 'Issue a certificate to credential a learner who has completed a course.'\n : 'Publish a course first, then issue certificates to learners who complete it.'\n }\n action={\n !isFiltered && hasCourses ? (\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Issue certificate\n </Button>\n ) : undefined\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(c) => c.id}\n defaultSort={{ key: 'issuedAt', dir: 'desc' }}\n />\n )}\n\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Issue certificate</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Credential a learner for completing a course. The serial is auto-generated but\n editable.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleIssue();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-course\">Course *</Label>\n <Select\n value={form.courseId}\n onValueChange={(v) => setForm((prev) => ({ ...prev, courseId: v }))}\n >\n <SelectTrigger id=\"cert-course\">\n <SelectValue placeholder=\"Select a course\" />\n </SelectTrigger>\n <SelectContent>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-learner\">Learner (user ID) *</Label>\n <Input\n id=\"cert-learner\"\n value={form.learnerId}\n onChange={(e) => setForm((prev) => ({ ...prev, learnerId: e.target.value }))}\n placeholder=\"Defaults to you\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-serial\">Serial *</Label>\n <Input\n id=\"cert-serial\"\n value={form.serial}\n onChange={(e) => setForm((prev) => ({ ...prev, serial: e.target.value }))}\n placeholder=\"BMIQ-XXXXXX\"\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-score\">Final score</Label>\n <Input\n id=\"cert-score\"\n type=\"number\"\n min=\"0\"\n step=\"0.1\"\n value={form.finalScore}\n onChange={(e) => setForm((prev) => ({ ...prev, finalScore: e.target.value }))}\n placeholder=\"e.g. 92\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-expires\">Expires</Label>\n <Input\n id=\"cert-expires\"\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => setForm((prev) => ({ ...prev, expiresAt: e.target.value }))}\n />\n </div>\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={issueCert.isPending}>\n <Download className=\"mr-1.5 h-4 w-4\" />\n {issueCert.isPending ? 'Issuing…' : 'Issue certificate'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CertificatesPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/certificates/CertificatesPage.tsx"],"sourcesContent":["/**\n * CertificatesPage — the credentialing surface for the workspace.\n * @module buildmyiq/pages/certificates\n *\n * The primary page of the Certificates section. Lists real `BuiCertificate`\n * rows (issued / expired / revoked) for the workspace, scoped by a \"Mine / All\"\n * learner toggle and an optional course filter. Facilitators can issue a new\n * certificate and revoke an issued one. Every value is derived from live `bui*`\n * data — no fabricated learners, scores, or serials.\n *\n * Reads: `buiListCertificates` (useBuiCertificates), `buiListCourses` (useBuiCourses).\n * Writes: `buiIssueCertificate` (useBuiIssueCertificate),\n * `buiRevokeCertificate` (useBuiRevokeCertificate).\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Plus, Award, Ban, Download, Search, ShieldCheck, CalendarClock } from 'lucide-react';\nimport {\n Button,\n Label,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n StatusBadge,\n DataTable,\n type Column,\n FilterTabs,\n type FilterOption,\n LoadingView,\n ErrorView,\n EmptyState,\n FileLink,\n Avatar,\n Pagination,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCertificates,\n useBuiCourses,\n useBuiIssueCertificate,\n useBuiRevokeCertificate,\n} from '../../hooks';\nimport { formatDate, formatPercent } from '../../utils/formatters';\nimport type { BuiCertificate } from '../../types';\n\ntype ScopeFilter = 'MINE' | 'ALL';\ntype CourseFilter = string | 'ALL';\n\nconst PAGE_SIZE = 8;\n\n/** Convert a native `<input type=\"date\">` value (YYYY-MM-DD) to a DateTime, or undefined. */\nconst toDateTime = (value: string): string | undefined => {\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n const d = new Date(trimmed);\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n};\n\n/** A collision-resistant, human-legible default serial for a new credential. */\nconst generateSerial = (): string => `BMIQ-${Date.now().toString(36).toUpperCase()}`;\n\ninterface IssueFormState {\n courseId: string;\n learnerId: string;\n serial: string;\n finalScore: string;\n expiresAt: string;\n}\n\nexport const CertificatesPage: FC = () => {\n const { currentUser } = useBuildmyiq();\n const selfId = currentUser?.id;\n\n const [scope, setScope] = useState<ScopeFilter>(selfId ? 'MINE' : 'ALL');\n const [courseFilter, setCourseFilter] = useState<CourseFilter>('ALL');\n const [search, setSearch] = useState('');\n const [page, setPage] = useState(0);\n const [dialogOpen, setDialogOpen] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [revokingId, setRevokingId] = useState<string | null>(null);\n\n const makeEmptyForm = (): IssueFormState => ({\n courseId: '',\n learnerId: selfId ?? '',\n serial: generateSerial(),\n finalScore: '',\n expiresAt: '',\n });\n const [form, setForm] = useState<IssueFormState>(makeEmptyForm);\n\n // Course lookup: powers the course filter, the issue-form course picker, and\n // the courseId → title mapping in the table.\n const coursesQuery = useBuiCourses({ limit: 100 });\n const courses = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\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 learnerId = scope === 'MINE' ? selfId : undefined;\n const courseId = courseFilter === 'ALL' ? undefined : courseFilter;\n\n const certsQuery = useBuiCertificates(courseId, learnerId);\n const issueCert = useBuiIssueCertificate();\n const revokeCert = useBuiRevokeCertificate();\n\n const rows = useMemo(() => certsQuery.data ?? [], [certsQuery.data]);\n\n // Client-side search over the loaded scope (serial / course / learner id).\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return rows;\n return rows.filter((c) => {\n const course = (courseTitleById.get(c.courseId) ?? '').toLowerCase();\n const learner = c.learnerId === selfId ? 'you me mine' : c.learnerId.toLowerCase();\n return (\n c.serial.toLowerCase().includes(q) || course.includes(q) || learner.includes(q)\n );\n });\n }, [rows, search, courseTitleById, selfId]);\n\n // Honest KPI counts across the loaded scope (before search narrowing).\n const summary = useMemo(() => {\n let issued = 0;\n let expired = 0;\n let revoked = 0;\n for (const c of rows) {\n if (c.status === 'ISSUED') issued += 1;\n else if (c.status === 'EXPIRED') expired += 1;\n else if (c.status === 'REVOKED') revoked += 1;\n }\n return { total: rows.length, issued, expired, revoked };\n }, [rows]);\n\n const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));\n\n // Derive an in-range page (avoids a setState-in-effect clamp loop): if filters/\n // search shrink the result set below the current page, fall back to the last page.\n const safePage = Math.min(page, pageCount - 1);\n\n const pageRows = useMemo(\n () => filtered.slice(safePage * PAGE_SIZE, safePage * PAGE_SIZE + PAGE_SIZE),\n [filtered, safePage]\n );\n\n const scopeOptions: FilterOption<ScopeFilter>[] = [\n { value: 'MINE', label: 'Mine' },\n { value: 'ALL', label: 'All' },\n ];\n\n const handleRevoke = async (cert: BuiCertificate): Promise<void> => {\n if (\n typeof window !== 'undefined' &&\n !window.confirm(`Revoke certificate ${cert.serial}? This cannot be undone.`)\n ) {\n return;\n }\n setActionError(null);\n setRevokingId(cert.id);\n try {\n await revokeCert.mutateAsync(cert.id);\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to revoke certificate.');\n } finally {\n setRevokingId(null);\n }\n };\n\n const columns: Column<BuiCertificate>[] = [\n {\n key: 'serial',\n header: 'Serial',\n sortValue: (c) => c.serial.toLowerCase(),\n render: (c) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)]\">\n <Award className=\"h-4 w-4\" />\n </div>\n <span className=\"min-w-0 truncate font-mono text-xs font-semibold text-text-primary\">\n {c.serial}\n </span>\n </div>\n ),\n },\n {\n key: 'course',\n header: 'Course',\n hideOnMobile: true,\n sortValue: (c) => (courseTitleById.get(c.courseId) ?? '').toLowerCase(),\n render: (c) => (\n <span className=\"text-text-secondary\">{courseTitleById.get(c.courseId) ?? '—'}</span>\n ),\n },\n {\n key: 'learner',\n header: 'Learner',\n sortValue: (c) => (c.learnerId === selfId ? '' : c.learnerId),\n render: (c) => {\n const isSelf = c.learnerId === selfId;\n const name = isSelf\n ? currentUser?.name ?? currentUser?.username ?? currentUser?.email ?? null\n : null;\n return (\n <div className=\"flex items-center gap-2.5\">\n <Avatar size=\"sm\" seed={c.learnerId} name={name} />\n {isSelf ? (\n <span className=\"font-medium text-text-primary\">You</span>\n ) : (\n <span className=\"font-mono text-xs text-text-tertiary\" title={c.learnerId}>\n {c.learnerId.slice(0, 8)}…\n </span>\n )}\n </div>\n );\n },\n },\n {\n key: 'status',\n header: 'Status',\n render: (c) => <StatusBadge status={c.status} />,\n },\n {\n key: 'issuedAt',\n header: 'Issued',\n hideOnMobile: true,\n sortValue: (c) => c.issuedAt,\n render: (c) => <span className=\"text-text-secondary\">{formatDate(c.issuedAt)}</span>,\n },\n {\n key: 'expiresAt',\n header: 'Expires',\n hideOnMobile: true,\n sortValue: (c) => c.expiresAt ?? '',\n render: (c) => <span className=\"text-text-tertiary\">{formatDate(c.expiresAt)}</span>,\n },\n {\n key: 'finalScore',\n header: 'Score',\n align: 'right',\n hideOnMobile: true,\n sortValue: (c) => c.finalScore ?? -1,\n render: (c) => (\n <span className=\"font-medium tabular-nums text-text-primary\">\n {c.finalScore != null ? formatPercent(c.finalScore) : '—'}\n </span>\n ),\n },\n {\n key: 'file',\n header: 'PDF',\n hideOnMobile: true,\n render: (c) =>\n c.file ? <FileLink fileId={c.file.id} /> : <span className=\"text-text-tertiary\">—</span>,\n },\n {\n key: 'actions',\n header: '',\n align: 'right',\n render: (c) =>\n c.status === 'ISSUED' ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-status-error-text hover:text-status-error-text\"\n disabled={revokingId === c.id}\n onClick={() => void handleRevoke(c)}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" />\n {revokingId === c.id ? 'Revoking…' : 'Revoke'}\n </Button>\n ) : null,\n },\n ];\n\n const openDialog = (): void => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = (): void => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const handleIssue = async (): Promise<void> => {\n if (!form.courseId) {\n setFormError('Please choose a course for this certificate.');\n return;\n }\n if (!form.learnerId.trim()) {\n setFormError('A learner (user ID) is required.');\n return;\n }\n if (!form.serial.trim()) {\n setFormError('A certificate serial is required.');\n return;\n }\n const scoreNum = form.finalScore.trim() ? Number(form.finalScore) : undefined;\n if (scoreNum !== undefined && (!Number.isFinite(scoreNum) || scoreNum < 0)) {\n setFormError('Final score must be a non-negative number.');\n return;\n }\n const expiresIso = toDateTime(form.expiresAt);\n\n setFormError(null);\n try {\n await issueCert.mutateAsync({\n courseId: form.courseId,\n learnerId: form.learnerId.trim(),\n serial: form.serial.trim(),\n finalScore: scoreNum,\n expiresAt: expiresIso,\n });\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to issue certificate.');\n }\n };\n\n const hasCourses = courses.length > 0;\n const isFiltered = scope === 'MINE' || courseFilter !== 'ALL' || search.trim().length > 0;\n const showSummary = !certsQuery.isLoading && !certsQuery.isError && summary.total > 0;\n\n return (\n <PageLayout\n title=\"Certificates\"\n description=\"Verifiable credentials issued to learners on course completion.\"\n titleGradient\n aurora\n actions={\n <Button onClick={openDialog} disabled={!hasCourses}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Issue certificate\n </Button>\n }\n >\n {showSummary && (\n <div className=\"mb-5 grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={summary.total}\n label=\"Certificates\"\n featured\n />\n <MetricsCard\n icon={<ShieldCheck className=\"h-5 w-5\" />}\n value={summary.issued}\n label=\"Issued\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<CalendarClock className=\"h-5 w-5\" />}\n value={summary.expired}\n label=\"Expired\"\n />\n <MetricsCard\n icon={<Ban className=\"h-5 w-5\" />}\n value={summary.revoked}\n label=\"Revoked\"\n accent=\"error\"\n />\n </div>\n )}\n\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={scopeOptions}\n value={scope}\n onChange={(v) => {\n setScope(v);\n setPage(0);\n }}\n />\n <Select\n value={courseFilter}\n onValueChange={(v) => {\n setCourseFilter(v as CourseFilter);\n setPage(0);\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by course\">\n <SelectValue placeholder=\"All courses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All courses</SelectItem>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <div className=\"relative w-full sm:ml-auto sm:w-64\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <Input\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(0);\n }}\n placeholder=\"Search certificates…\"\n aria-label=\"Search certificates\"\n className=\"pl-9\"\n />\n </div>\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {certsQuery.isLoading ? (\n <LoadingView label=\"Loading certificates…\" />\n ) : certsQuery.isError ? (\n <ErrorView message=\"Couldn't load certificates.\" onRetry={() => certsQuery.refetch()} />\n ) : filtered.length === 0 ? (\n <EmptyState\n illustration={isFiltered ? 'empty-search' : 'onboarding'}\n icon={<Award className=\"h-10 w-10\" />}\n title={isFiltered ? 'No certificates match these filters' : 'No certificates issued yet'}\n description={\n isFiltered\n ? search.trim().length > 0\n ? 'No credentials match your search. Try a different serial, course, or learner.'\n : scope === 'MINE'\n ? \"You haven't earned a certificate for this selection yet.\"\n : 'No certificates have been issued for this course yet.'\n : hasCourses\n ? 'Issue a certificate to credential a learner who has completed a course.'\n : 'Publish a course first, then issue certificates to learners who complete it.'\n }\n action={\n !isFiltered && hasCourses ? (\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Issue certificate\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={pageRows}\n rowKey={(c) => c.id}\n defaultSort={{ key: 'issuedAt', dir: 'desc' }}\n />\n <Pagination\n page={safePage}\n pageSize={PAGE_SIZE}\n itemCount={pageRows.length}\n hasMore={(safePage + 1) * PAGE_SIZE < filtered.length}\n onPageChange={setPage}\n />\n </>\n )}\n\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Issue certificate</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Credential a learner for completing a course. The serial is auto-generated but\n editable.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleIssue();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-course\">Course *</Label>\n <Select\n value={form.courseId}\n onValueChange={(v) => setForm((prev) => ({ ...prev, courseId: v }))}\n >\n <SelectTrigger id=\"cert-course\">\n <SelectValue placeholder=\"Select a course\" />\n </SelectTrigger>\n <SelectContent>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-learner\">Learner (user ID) *</Label>\n <Input\n id=\"cert-learner\"\n value={form.learnerId}\n onChange={(e) => setForm((prev) => ({ ...prev, learnerId: e.target.value }))}\n placeholder=\"Defaults to you\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-serial\">Serial *</Label>\n <Input\n id=\"cert-serial\"\n value={form.serial}\n onChange={(e) => setForm((prev) => ({ ...prev, serial: e.target.value }))}\n placeholder=\"BMIQ-XXXXXX\"\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-score\">Final score</Label>\n <Input\n id=\"cert-score\"\n type=\"number\"\n min=\"0\"\n step=\"0.1\"\n value={form.finalScore}\n onChange={(e) => setForm((prev) => ({ ...prev, finalScore: e.target.value }))}\n placeholder=\"e.g. 92\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"cert-expires\">Expires</Label>\n <Input\n id=\"cert-expires\"\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => setForm((prev) => ({ ...prev, expiresAt: e.target.value }))}\n />\n </div>\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={issueCert.isPending}>\n <Download className=\"mr-1.5 h-4 w-4\" />\n {issueCert.isPending ? 'Issuing…' : 'Issue certificate'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CohortsPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/cohorts/CohortsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAsDnD,eAAO,MAAM,WAAW,EAAE,EAiWzB,CAAC"}
1
+ {"version":3,"file":"CohortsPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/cohorts/CohortsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAyEnD,eAAO,MAAM,WAAW,EAAE,EA4azB,CAAC"}