@burdenoff/microfe-buildmyiq 2026.705.1 → 2026.705.2
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/context/BuildmyiqContext.js.map +1 -1
- package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
- package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
- package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
- package/dist/buildmyiq/types/index.js.map +1 -1
- package/dist/buildmyiq/utils/cn.js +2 -2
- package/dist/buildmyiq/utils/formatters.js.map +1 -1
- package/dist/node_modules/{.bun/clsx@2.1.1/node_modules/clsx → clsx}/dist/clsx.js +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/{.bun/tailwind-merge@3.5.0/node_modules/tailwind-merge → tailwind-merge}/dist/bundle-mjs.js +212 -170
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +1 -1
- package/dist/buildmyiq/BuildmyiqRoot.d.ts +0 -5
- package/dist/buildmyiq/BuildmyiqRoot.d.ts.map +0 -1
- package/dist/buildmyiq/BuildmyiqRoutes.d.ts +0 -4
- package/dist/buildmyiq/BuildmyiqRoutes.d.ts.map +0 -1
- package/dist/buildmyiq/components/BuiDiscussion.d.ts +0 -18
- package/dist/buildmyiq/components/BuiDiscussion.d.ts.map +0 -1
- package/dist/buildmyiq/components/BuiTags.d.ts +0 -17
- package/dist/buildmyiq/components/BuiTags.d.ts.map +0 -1
- package/dist/buildmyiq/components/DataTable.d.ts +0 -28
- package/dist/buildmyiq/components/DataTable.d.ts.map +0 -1
- package/dist/buildmyiq/components/EmptyState.d.ts +0 -18
- package/dist/buildmyiq/components/EmptyState.d.ts.map +0 -1
- package/dist/buildmyiq/components/EvidenceUpload.d.ts +0 -14
- package/dist/buildmyiq/components/EvidenceUpload.d.ts.map +0 -1
- package/dist/buildmyiq/components/FileLink.d.ts +0 -15
- package/dist/buildmyiq/components/FileLink.d.ts.map +0 -1
- package/dist/buildmyiq/components/FilterTabs.d.ts +0 -18
- package/dist/buildmyiq/components/FilterTabs.d.ts.map +0 -1
- package/dist/buildmyiq/components/MetricsCard.d.ts +0 -23
- package/dist/buildmyiq/components/MetricsCard.d.ts.map +0 -1
- package/dist/buildmyiq/components/MiniBar.d.ts +0 -16
- package/dist/buildmyiq/components/MiniBar.d.ts.map +0 -1
- package/dist/buildmyiq/components/PageLayout.d.ts +0 -16
- package/dist/buildmyiq/components/PageLayout.d.ts.map +0 -1
- package/dist/buildmyiq/components/Pagination.d.ts +0 -22
- package/dist/buildmyiq/components/Pagination.d.ts.map +0 -1
- package/dist/buildmyiq/components/StateViews.d.ts +0 -24
- package/dist/buildmyiq/components/StateViews.d.ts.map +0 -1
- package/dist/buildmyiq/components/StatusBadge.d.ts +0 -9
- package/dist/buildmyiq/components/StatusBadge.d.ts.map +0 -1
- package/dist/buildmyiq/components/Tabs.d.ts +0 -17
- package/dist/buildmyiq/components/Tabs.d.ts.map +0 -1
- package/dist/buildmyiq/components/index.d.ts +0 -19
- package/dist/buildmyiq/components/index.d.ts.map +0 -1
- package/dist/buildmyiq/context/BuildmyiqContext.d.ts +0 -16
- package/dist/buildmyiq/context/BuildmyiqContext.d.ts.map +0 -1
- package/dist/buildmyiq/context/index.d.ts +0 -6
- package/dist/buildmyiq/context/index.d.ts.map +0 -1
- package/dist/buildmyiq/hooks/index.d.ts +0 -10
- package/dist/buildmyiq/hooks/index.d.ts.map +0 -1
- package/dist/buildmyiq/hooks/useBuiComments.d.ts +0 -36
- package/dist/buildmyiq/hooks/useBuiComments.d.ts.map +0 -1
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts +0 -47
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts.map +0 -1
- package/dist/buildmyiq/hooks/useBuiTags.d.ts +0 -44
- package/dist/buildmyiq/hooks/useBuiTags.d.ts.map +0 -1
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts +0 -65
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +0 -1
- package/dist/buildmyiq/hooks/useCursorPager.d.ts +0 -33
- package/dist/buildmyiq/hooks/useCursorPager.d.ts.map +0 -1
- package/dist/buildmyiq/index.d.ts +0 -9
- package/dist/buildmyiq/index.d.ts.map +0 -1
- package/dist/buildmyiq/pages/DashboardPage.d.ts +0 -4
- package/dist/buildmyiq/pages/DashboardPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/_shared/CourseCard.d.ts +0 -13
- package/dist/buildmyiq/pages/_shared/CourseCard.d.ts.map +0 -1
- package/dist/buildmyiq/pages/_shared/index.d.ts +0 -6
- package/dist/buildmyiq/pages/_shared/index.d.ts.map +0 -1
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts +0 -4
- package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts +0 -4
- package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts +0 -4
- package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts +0 -4
- package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/authoring/_forms.d.ts +0 -44
- package/dist/buildmyiq/pages/authoring/_forms.d.ts.map +0 -1
- package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts +0 -4
- package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts +0 -4
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts +0 -4
- package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts +0 -4
- package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts +0 -4
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts +0 -4
- package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/skills/SkillsPage.d.ts +0 -4
- package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +0 -1
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts +0 -4
- package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +0 -1
- package/dist/buildmyiq/types/index.d.ts +0 -382
- package/dist/buildmyiq/types/index.d.ts.map +0 -1
- package/dist/buildmyiq/utils/cn.d.ts +0 -4
- package/dist/buildmyiq/utils/cn.d.ts.map +0 -1
- package/dist/buildmyiq/utils/formatters.d.ts +0 -33
- package/dist/buildmyiq/utils/formatters.d.ts.map +0 -1
- package/dist/buildmyiq/utils/i18n.d.ts +0 -6
- package/dist/buildmyiq/utils/i18n.d.ts.map +0 -1
- package/dist/buildmyiq/utils/index.d.ts +0 -9
- package/dist/buildmyiq/utils/index.d.ts.map +0 -1
- package/dist/buildmyiq/utils/navigation.d.ts +0 -14
- package/dist/buildmyiq/utils/navigation.d.ts.map +0 -1
- package/dist/dev/main.d.ts +0 -2
- package/dist/dev/main.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +0 -1
- package/dist/node_modules/.bun/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubmissionsPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/submissions/SubmissionsPage.tsx"],"sourcesContent":["/**\n * SubmissionsPage — the workspace-wide grading queue.\n * @module buildmyiq/pages/submissions\n *\n * The educator surface for assessment submissions. Reads every submission in the\n * workspace via `useBuiSubmissions` (no `assessmentId` → all assessments),\n * filterable by status, and paginated by cursor. Clicking a row opens a grading\n * drawer that shows the learner's answers read-only (rendered safely from the\n * `answers` JSON blob) and — when the submission is awaiting grading — a grading\n * form (`useBuiGradeSubmission`: score, passed, feedback). Already-graded rows\n * show their result read-only with a \"Re-grade\" affordance.\n *\n * Every number is derived from live `bui*` data; the KPI row is explicitly scoped\n * to the current page (\"this page\") so it never overstates workspace totals.\n */\n\nimport { useMemo, useState } from 'react';\nimport type { FC, ReactNode } from 'react';\nimport { ClipboardList, Clock, CheckCircle2, Undo2, GraduationCap } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Switch,\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 FilterTabs,\n Pagination,\n EmptyState,\n LoadingView,\n ErrorView,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiSubmissions, useBuiGradeSubmission, useCursorPager } from '../../hooks';\nimport { formatDateTime } from '../../utils/formatters';\nimport type { BuiSubmission, BuiSubmissionStatus } from '../../types';\n\nconst PAGE_SIZE = 24;\n\ntype StatusFilter = BuiSubmissionStatus | 'ALL';\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SUBMITTED', label: 'Submitted' },\n { value: 'GRADING', label: 'Grading' },\n { value: 'GRADED', label: 'Graded' },\n { value: 'RETURNED', label: 'Returned' },\n];\n\n/** Truncate an opaque id for compact display, keeping the full value in `title`. */\nconst shortId = (id: string): string => (id.length > 10 ? `${id.slice(0, 8)}…` : id);\n\nconst isPlainRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\n/** Render a single answer value safely, whatever its JSON shape. */\nconst formatAnswerValue = (v: unknown): string => {\n if (v == null) return '—';\n if (typeof v === 'string') return v;\n if (typeof v === 'number' || typeof v === 'boolean') return String(v);\n return JSON.stringify(v, null, 2);\n};\n\n/** Read-only rendering of a submission's `answers` JSON blob. */\nconst AnswersView: FC<{ answers: unknown }> = ({ answers }) => {\n if (answers == null) {\n return <p className=\"text-sm text-text-secondary\">No answers were submitted.</p>;\n }\n if (isPlainRecord(answers)) {\n const entries = Object.entries(answers);\n if (entries.length === 0) {\n return <p className=\"text-sm text-text-secondary\">No answers were submitted.</p>;\n }\n return (\n <dl className=\"divide-y divide-border-seam/60\">\n {entries.map(([key, value]) => (\n <div key={key} className=\"grid gap-1 py-2 sm:grid-cols-3 sm:gap-3\">\n <dt className=\"break-words font-mono text-xs text-text-secondary sm:col-span-1\">\n {key}\n </dt>\n <dd className=\"whitespace-pre-wrap break-words text-sm text-text-primary sm:col-span-2\">\n {formatAnswerValue(value)}\n </dd>\n </div>\n ))}\n </dl>\n );\n }\n return (\n <pre className=\"overflow-x-auto whitespace-pre-wrap break-words text-xs text-text-primary\">\n {JSON.stringify(answers, null, 2)}\n </pre>\n );\n};\n\n/** A labelled read-only field for the submission meta / graded-result panels. */\nconst MetaField: FC<{ label: string; children: ReactNode }> = ({ label, children }) => (\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <div className=\"mt-0.5 text-sm text-text-primary\">{children}</div>\n </div>\n);\n\nexport const SubmissionsPage: FC = () => {\n const { currentUser } = useBuildmyiq();\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const pager = useCursorPager();\n\n const [selected, setSelected] = useState<BuiSubmission | null>(null);\n const [editMode, setEditMode] = useState(false);\n const [scoreInput, setScoreInput] = useState('');\n const [passed, setPassed] = useState(false);\n const [feedback, setFeedback] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useBuiSubmissions({\n status: statusFilter === 'ALL' ? undefined : statusFilter,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n const gradeSubmission = useBuiGradeSubmission();\n\n const rows = useMemo<BuiSubmission[]>(() => data?.items ?? [], [data]);\n\n // Counts are scoped to the loaded page — labelled \"this page\" to stay honest.\n const counts = useMemo(() => {\n const acc = { total: rows.length, awaiting: 0, graded: 0, returned: 0 };\n for (const s of rows) {\n if (s.status === 'SUBMITTED' || s.status === 'GRADING') acc.awaiting += 1;\n else if (s.status === 'GRADED') acc.graded += 1;\n else if (s.status === 'RETURNED') acc.returned += 1;\n }\n return acc;\n }, [rows]);\n\n const openGrade = (s: BuiSubmission) => {\n setSelected(s);\n setScoreInput(s.score != null ? String(s.score) : '');\n setPassed(s.passed ?? false);\n setFeedback(s.feedback ?? '');\n setEditMode(false);\n setFormError(null);\n };\n\n const closeModal = () => {\n setSelected(null);\n setEditMode(false);\n setFormError(null);\n };\n\n const handleFilterChange = (value: StatusFilter) => {\n setStatusFilter(value);\n pager.reset();\n };\n\n const handleGrade = async () => {\n if (!selected) return;\n const trimmed = scoreInput.trim();\n const scoreNum = Number(trimmed);\n if (trimmed === '' || Number.isNaN(scoreNum)) {\n setFormError('Enter a numeric score.');\n return;\n }\n if (scoreNum < 0) {\n setFormError('Score cannot be negative.');\n return;\n }\n try {\n await gradeSubmission.mutateAsync({\n id: selected.id,\n input: { score: scoreNum, passed, feedback: feedback.trim() || undefined },\n });\n closeModal();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to save grade.');\n }\n };\n\n const gradingAllowed =\n selected != null && (selected.status === 'SUBMITTED' || selected.status === 'GRADING');\n const alreadyGraded =\n selected != null && (selected.status === 'GRADED' || selected.status === 'RETURNED');\n const notSubmitted = selected != null && selected.status === 'IN_PROGRESS';\n const showGradeForm = selected != null && (gradingAllowed || editMode);\n\n const columns: Column<BuiSubmission>[] = [\n {\n key: 'learner',\n header: 'Learner',\n sortValue: (s) => s.learnerId,\n render: (s) =>\n s.learnerId === currentUser?.id ? (\n <span className=\"font-medium text-text-primary\">You</span>\n ) : (\n <span className=\"font-mono text-xs text-text-secondary\" title={s.learnerId}>\n {shortId(s.learnerId)}\n </span>\n ),\n },\n {\n key: 'assessment',\n header: 'Assessment',\n hideOnMobile: true,\n render: (s) => (\n <span className=\"font-mono text-xs text-text-secondary\" title={s.assessmentId}>\n {shortId(s.assessmentId)}\n </span>\n ),\n },\n {\n key: 'attempt',\n header: 'Attempt',\n align: 'right',\n hideOnMobile: true,\n sortValue: (s) => s.attempt,\n render: (s) => <span className=\"tabular-nums text-text-secondary\">#{s.attempt}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n sortValue: (s) => s.status,\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'score',\n header: 'Score',\n align: 'right',\n sortValue: (s) => s.score ?? -1,\n render: (s) => (\n <span className=\"tabular-nums font-medium text-text-primary\">{s.score ?? '—'}</span>\n ),\n },\n {\n key: 'submittedAt',\n header: 'Submitted',\n hideOnMobile: true,\n sortValue: (s) => s.submittedAt ?? s.createdAt,\n render: (s) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(s.submittedAt)}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Grading\"\n description=\"Review and grade assessment submissions across every course in your workspace.\"\n titleGradient\n aurora\n >\n <div className=\"flex flex-col gap-6\">\n {/* KPI row — scoped to the current page. */}\n <div className=\"grid grid-cols-2 gap-4 xl:grid-cols-4\">\n <MetricsCard\n icon={<ClipboardList className=\"h-5 w-5\" />}\n value={counts.total}\n label=\"Submissions (this page)\"\n featured\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={counts.awaiting}\n label=\"Awaiting grade (this page)\"\n accent=\"warning\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={counts.graded}\n label=\"Graded (this page)\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Undo2 className=\"h-5 w-5\" />}\n value={counts.returned}\n label=\"Returned (this page)\"\n />\n </div>\n\n <FilterTabs\n options={STATUS_FILTERS}\n value={statusFilter}\n onChange={handleFilterChange}\n />\n\n {isLoading ? (\n <LoadingView label=\"Loading submissions…\" />\n ) : isError ? (\n <ErrorView\n title=\"Couldn't load submissions\"\n message=\"The grading queue failed to load.\"\n onRetry={() => refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<GraduationCap className=\"h-10 w-10\" />}\n title={statusFilter === 'ALL' ? 'No submissions yet' : 'Nothing in this status'}\n description={\n statusFilter === 'ALL'\n ? 'When learners submit assessments, their attempts will appear here for grading.'\n : 'No submissions currently match this status filter.'\n }\n />\n ) : (\n <div>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n onRowClick={openGrade}\n defaultSort={{ key: 'submittedAt', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo?.cursor)}\n />\n </div>\n )}\n </div>\n\n <ResponsiveDialog\n open={selected != null}\n onOpenChange={(open) => {\n if (!open) closeModal();\n }}\n >\n <ResponsiveDialogContent className=\"max-h-[88vh] overflow-y-auto sm:max-w-[640px]\">\n {selected && (\n <>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {gradingAllowed || editMode ? 'Grade submission' : 'Submission details'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {selected.learnerId === currentUser?.id\n ? 'Your attempt'\n : `Learner ${shortId(selected.learnerId)}`}{' '}\n · attempt #{selected.attempt}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-5 py-2\">\n {/* Meta */}\n <div className=\"grid grid-cols-2 gap-4 rounded-xl bg-bg-sunken/60 p-4 sm:grid-cols-3\">\n <MetaField label=\"Status\">\n <StatusBadge status={selected.status} />\n </MetaField>\n <MetaField label=\"Attempt\">\n <span className=\"tabular-nums\">#{selected.attempt}</span>\n </MetaField>\n <MetaField label=\"Submitted\">{formatDateTime(selected.submittedAt)}</MetaField>\n <MetaField label=\"Assessment\">\n <span className=\"font-mono text-xs\" title={selected.assessmentId}>\n {shortId(selected.assessmentId)}\n </span>\n </MetaField>\n {selected.enrollmentId && (\n <MetaField label=\"Enrollment\">\n <span className=\"font-mono text-xs\" title={selected.enrollmentId}>\n {shortId(selected.enrollmentId)}\n </span>\n </MetaField>\n )}\n {selected.gradedAt && (\n <MetaField label=\"Graded\">{formatDateTime(selected.gradedAt)}</MetaField>\n )}\n </div>\n\n {/* Learner answers (read-only) */}\n <div>\n <p className=\"mb-2 text-sm font-semibold text-text-primary\">Answers</p>\n <div className=\"max-h-64 overflow-y-auto rounded-xl border border-border-seam p-4\">\n <AnswersView answers={selected.answers} />\n </div>\n </div>\n\n {/* Not-yet-submitted note */}\n {notSubmitted && (\n <p className=\"rounded-lg bg-bg-sunken/60 px-4 py-3 text-sm text-text-secondary\">\n This attempt is still in progress — there is nothing to grade until the learner\n submits.\n </p>\n )}\n\n {/* Existing grade (read-only) with a re-grade affordance */}\n {alreadyGraded && !editMode && (\n <div className=\"rounded-xl border border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"grid grid-cols-2 gap-4\">\n <MetaField label=\"Score\">\n <span className=\"tabular-nums font-semibold\">\n {selected.score ?? '—'}\n </span>\n </MetaField>\n <MetaField label=\"Result\">\n {selected.passed == null ? (\n <span className=\"text-text-secondary\">—</span>\n ) : (\n <StatusBadge status={selected.passed ? 'GRADED' : 'RETURNED'} />\n )}\n </MetaField>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditMode(true)}>\n Re-grade\n </Button>\n </div>\n <div className=\"mt-3\">\n <MetaField label=\"Feedback\">\n {selected.feedback ? (\n <span className=\"whitespace-pre-wrap\">{selected.feedback}</span>\n ) : (\n <span className=\"text-text-secondary\">No feedback provided.</span>\n )}\n </MetaField>\n </div>\n </div>\n )}\n\n {/* Grading form */}\n {showGradeForm && (\n <div className=\"grid gap-4\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade-score\">Score</Label>\n <Input\n id=\"grade-score\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={scoreInput}\n onChange={(e) => setScoreInput(e.target.value)}\n placeholder=\"e.g. 85\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade-passed\">Result</Label>\n <div className=\"flex h-9 items-center gap-3\">\n <Switch\n id=\"grade-passed\"\n checked={passed}\n onCheckedChange={setPassed}\n />\n <span className=\"text-sm text-text-secondary\">\n {passed ? 'Passed' : 'Not passed'}\n </span>\n </div>\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade-feedback\">Feedback</Label>\n <Textarea\n id=\"grade-feedback\"\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder=\"Notes, corrections or encouragement for the learner (optional).\"\n rows={3}\n />\n </div>\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeModal}>\n {showGradeForm ? 'Cancel' : 'Close'}\n </Button>\n {showGradeForm && (\n <Button onClick={handleGrade} disabled={gradeSubmission.isPending}>\n {gradeSubmission.isPending\n ? 'Saving…'\n : alreadyGraded\n ? 'Update grade'\n : 'Save grade'}\n </Button>\n )}\n </ResponsiveDialogFooter>\n </>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkDA,IAAM,IAAY,IAIZ,KAA+C;CACnD;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC,EAGK,KAAW,MAAwB,EAAG,SAAS,KAAK,GAAG,EAAG,MAAM,GAAG,EAAE,CAAC,KAAK,GAE3E,KAAiB,MACrB,OAAO,KAAM,cAAY,KAAc,CAAC,MAAM,QAAQ,EAAE,EAGpD,KAAqB,MACrB,KAAK,OAAa,MAClB,OAAO,KAAM,WAAiB,IAC9B,OAAO,KAAM,YAAY,OAAO,KAAM,YAAkB,OAAO,EAAE,GAC9D,KAAK,UAAU,GAAG,MAAM,EAAE,EAI7B,KAAyC,EAAE,iBAAc;AAC7D,KAAI,KAAW,KACb,QAAO,kBAAC,KAAD;EAAG,WAAU;YAA8B;EAA8B,CAAA;AAElF,KAAI,EAAc,EAAQ,EAAE;EAC1B,IAAM,IAAU,OAAO,QAAQ,EAAQ;AAIvC,SAHI,EAAQ,WAAW,IACd,kBAAC,KAAD;GAAG,WAAU;aAA8B;GAA8B,CAAA,GAGhF,kBAAC,MAAD;GAAI,WAAU;aACX,EAAQ,KAAK,CAAC,GAAK,OAClB,kBAAC,OAAD;IAAe,WAAU;cAAzB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX;KACE,CAAA,EACL,kBAAC,MAAD;KAAI,WAAU;eACX,EAAkB,EAAM;KACtB,CAAA,CACD;MAPI,EAOJ,CACN;GACC,CAAA;;AAGT,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,KAAK,UAAU,GAAS,MAAM,EAAE;EAC7B,CAAA;GAKJ,KAAyD,EAAE,UAAO,kBACtE,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,KAAD;EAAG,WAAU;YAAmE;EAAU,CAAA,EAC1F,kBAAC,OAAD;EAAK,WAAU;EAAoC;EAAe,CAAA,CAC9D;IAGK,UAA4B;CACvC,IAAM,EAAE,mBAAgB,GAAc,EAChC,CAAC,GAAc,KAAmB,EAAuB,MAAM,EAC/D,IAAQ,IAAgB,EAExB,CAAC,GAAU,KAAe,EAA+B,KAAK,EAC9D,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,EAAE,SAAM,eAAW,aAAS,gBAAY,GAAkB;EAC9D,QAAQ,MAAiB,QAAQ,KAAA,IAAY;EAC7C,QAAQ,EAAM;EACd,OAAO;EACR,CAAC,EACI,IAAkB,GAAuB,EAEzC,IAAO,QAA+B,GAAM,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAGhE,IAAS,QAAc;EAC3B,IAAM,IAAM;GAAE,OAAO,EAAK;GAAQ,UAAU;GAAG,QAAQ;GAAG,UAAU;GAAG;AACvE,OAAK,IAAM,KAAK,EACd,CAAI,EAAE,WAAW,eAAe,EAAE,WAAW,YAAW,EAAI,YAAY,IAC/D,EAAE,WAAW,WAAU,EAAI,UAAU,IACrC,EAAE,WAAW,eAAY,EAAI,YAAY;AAEpD,SAAO;IACN,CAAC,EAAK,CAAC,EAEJ,MAAa,MAAqB;AAMtC,EALA,EAAY,EAAE,EACd,EAAc,EAAE,SAAS,OAAyB,KAAlB,OAAO,EAAE,MAAM,CAAM,EACrD,EAAU,EAAE,UAAU,GAAM,EAC5B,EAAY,EAAE,YAAY,GAAG,EAC7B,EAAY,GAAM,EAClB,EAAa,KAAK;IAGd,UAAmB;AAGvB,EAFA,EAAY,KAAK,EACjB,EAAY,GAAM,EAClB,EAAa,KAAK;IAGd,MAAsB,MAAwB;AAElD,EADA,EAAgB,EAAM,EACtB,EAAM,OAAO;IAGT,KAAc,YAAY;AAC9B,MAAI,CAAC,EAAU;EACf,IAAM,IAAU,EAAW,MAAM,EAC3B,IAAW,OAAO,EAAQ;AAChC,MAAI,MAAY,MAAM,OAAO,MAAM,EAAS,EAAE;AAC5C,KAAa,yBAAyB;AACtC;;AAEF,MAAI,IAAW,GAAG;AAChB,KAAa,4BAA4B;AACzC;;AAEF,MAAI;AAKF,GAJA,MAAM,EAAgB,YAAY;IAChC,IAAI,EAAS;IACb,OAAO;KAAE,OAAO;KAAU;KAAQ,UAAU,EAAS,MAAM,IAAI,KAAA;KAAW;IAC3E,CAAC,EACF,GAAY;WACL,GAAK;AACZ,KAAa,aAAe,QAAQ,EAAI,UAAU,wBAAwB;;IAIxE,IACJ,KAAY,SAAS,EAAS,WAAW,eAAe,EAAS,WAAW,YACxE,IACJ,KAAY,SAAS,EAAS,WAAW,YAAY,EAAS,WAAW,aACrE,KAAe,KAAY,QAAQ,EAAS,WAAW,eACvD,IAAgB,KAAY,SAAS,KAAkB;AA8D7D,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,eAAA;EACA,QAAA;YAJF,CAME,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;OAC3C,OAAO,EAAO;OACd,OAAM;OACN,UAAA;OACA,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACnC,OAAO,EAAO;OACd,OAAM;OACN,QAAO;OACP,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;OAC1C,OAAO,EAAO;OACd,OAAM;OACN,QAAO;OACP,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;OACnC,OAAO,EAAO;OACd,OAAM;OACN,CAAA;MACE;;IAEN,kBAAC,GAAD;KACE,SAAS;KACT,OAAO;KACP,UAAU;KACV,CAAA;IAED,KACC,kBAAC,IAAD,EAAa,OAAM,wBAAyB,CAAA,GAC1C,KACF,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,eAAe,IAAS;KACxB,CAAA,GACA,EAAK,WAAW,IAClB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAe,WAAU,aAAc,CAAA;KAC7C,OAAO,MAAiB,QAAQ,uBAAuB;KACvD,aACE,MAAiB,QACb,mFACA;KAEN,CAAA,GAEF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,IAAD;KACW,SA3HoB;MACvC;OACE,KAAK;OACL,QAAQ;OACR,YAAY,MAAM,EAAE;OACpB,SAAS,MACP,EAAE,cAAc,GAAa,KAC3B,kBAAC,QAAD;QAAM,WAAU;kBAAgC;QAAU,CAAA,GAE1D,kBAAC,QAAD;QAAM,WAAU;QAAwC,OAAO,EAAE;kBAC9D,EAAQ,EAAE,UAAU;QAChB,CAAA;OAEZ;MACD;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;QAAwC,OAAO,EAAE;kBAC9D,EAAQ,EAAE,aAAa;QACnB,CAAA;OAEV;MACD;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,cAAc;OACd,YAAY,MAAM,EAAE;OACpB,SAAS,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAAmD,KAAE,EAAE,QAAe;;OACtF;MACD;OACE,KAAK;OACL,QAAQ;OACR,YAAY,MAAM,EAAE;OACpB,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;OACjD;MACD;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,YAAY,MAAM,EAAE,SAAS;OAC7B,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBAA8C,EAAE,SAAS;QAAW,CAAA;OAEvF;MACD;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,YAAY,MAAM,EAAE,eAAe,EAAE;OACrC,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAe,EAAE,YAAY;QACzB,CAAA;OAEV;MACF;KAkEiB;KACN,SAAS,MAAM,EAAE;KACjB,YAAY;KACZ,aAAa;MAAE,KAAK;MAAe,KAAK;MAAQ;KAChD,CAAA,EACF,kBAAC,GAAD;KACE,MAAM,EAAM;KACZ,UAAU;KACV,WAAW,EAAK;KAChB,SAAS,GAAM,UAAU,WAAW;KACpC,eAAe,MAAW,EAAM,aAAa,GAAQ,GAAM,UAAU,OAAO;KAC5E,CAAA,CACE,EAAA,CAAA;IAEJ;MAEN,kBAAC,GAAD;GACE,MAAM,KAAY;GAClB,eAAe,MAAS;AACtB,IAAK,KAAM,GAAY;;aAGzB,kBAAC,GAAD;IAAyB,WAAU;cAChC,KACC,kBAAA,IAAA,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UACG,KAAkB,IAAW,qBAAqB,sBAC7B,CAAA,EACxB,kBAAC,GAAD,EAAA,UAAA;MACG,EAAS,cAAc,GAAa,KACjC,iBACA,WAAW,EAAQ,EAAS,UAAU;MAAI;MAAI;MACtC,EAAS;MACO,EAAA,CAAA,CACP,EAAA,CAAA;KAEzB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAW,OAAM;oBACf,kBAAC,GAAD,EAAa,QAAQ,EAAS,QAAU,CAAA;UAC9B,CAAA;SACZ,kBAAC,GAAD;UAAW,OAAM;oBACf,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CAA+B,KAAE,EAAS,QAAe;;UAC/C,CAAA;SACZ,kBAAC,GAAD;UAAW,OAAM;oBAAa,EAAe,EAAS,YAAY;UAAa,CAAA;SAC/E,kBAAC,GAAD;UAAW,OAAM;oBACf,kBAAC,QAAD;WAAM,WAAU;WAAoB,OAAO,EAAS;qBACjD,EAAQ,EAAS,aAAa;WAC1B,CAAA;UACG,CAAA;SACX,EAAS,gBACR,kBAAC,GAAD;UAAW,OAAM;oBACf,kBAAC,QAAD;WAAM,WAAU;WAAoB,OAAO,EAAS;qBACjD,EAAQ,EAAS,aAAa;WAC1B,CAAA;UACG,CAAA;SAEb,EAAS,YACR,kBAAC,GAAD;UAAW,OAAM;oBAAU,EAAe,EAAS,SAAS;UAAa,CAAA;SAEvE;;OAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAA+C;QAAW,CAAA,EACvE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAa,SAAS,EAAS,SAAW,CAAA;QACtC,CAAA,CACF,EAAA,CAAA;OAGL,MACC,kBAAC,KAAD;QAAG,WAAU;kBAAmE;QAG5E,CAAA;OAIL,KAAiB,CAAC,KACjB,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAW,OAAM;qBACf,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAS,SAAS;YACd,CAAA;WACG,CAAA,EACZ,kBAAC,GAAD;WAAW,OAAM;qBACd,EAAS,UAAU,OAClB,kBAAC,QAAD;YAAM,WAAU;sBAAsB;YAAQ,CAAA,GAE9C,kBAAC,GAAD,EAAa,QAAQ,EAAS,SAAS,WAAW,YAAc,CAAA;WAExD,CAAA,CACR;aACN,kBAAC,GAAD;UAAQ,SAAQ;UAAU,MAAK;UAAK,eAAe,EAAY,GAAK;oBAAE;UAE7D,CAAA,CACL;YACN,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD;UAAW,OAAM;oBACd,EAAS,WACR,kBAAC,QAAD;WAAM,WAAU;qBAAuB,EAAS;WAAgB,CAAA,GAEhE,kBAAC,QAAD;WAAM,WAAU;qBAAsB;WAA4B,CAAA;UAE1D,CAAA;SACR,CAAA,CACF;;OAIP,KACC,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD;YAAO,SAAQ;sBAAc;YAAa,CAAA,EAC1C,kBAAC,GAAD;YACE,IAAG;YACH,MAAK;YACL,KAAI;YACJ,MAAK;YACL,OAAO;YACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;YAC9C,aAAY;YACZ,CAAA,CACE;cACN,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD;YAAO,SAAQ;sBAAe;YAAc,CAAA,EAC5C,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,IAAD;aACE,IAAG;aACH,SAAS;aACT,iBAAiB;aACjB,CAAA,EACF,kBAAC,QAAD;aAAM,WAAU;uBACb,IAAS,WAAW;aAChB,CAAA,CACH;cACF;aACF;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAiB;WAAgB,CAAA,EAChD,kBAAC,IAAD;WACE,IAAG;WACH,OAAO;WACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;WAC5C,aAAY;WACZ,MAAM;WACN,CAAA,CACE;;SACL,KAAa,kBAAC,KAAD;UAAG,WAAU;oBAAkC;UAAc,CAAA;SACvE;;OAEJ;;KAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,SAAS;gBAChC,IAAgB,WAAW;MACrB,CAAA,EACR,KACC,kBAAC,GAAD;MAAQ,SAAS;MAAa,UAAU,EAAgB;gBACrD,EAAgB,YACb,YACA,IACE,iBACA;MACC,CAAA,CAEY,EAAA,CAAA;KACxB,EAAA,CAAA;IAEmB,CAAA;GACT,CAAA,CACR"}
|
|
1
|
+
{"version":3,"file":"SubmissionsPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/submissions/SubmissionsPage.tsx"],"sourcesContent":["/**\n * SubmissionsPage — the workspace-wide grading queue.\n * @module buildmyiq/pages/submissions\n *\n * The educator surface for assessment submissions. Reads every submission in the\n * workspace via `useBuiSubmissions` (no `assessmentId` → all assessments),\n * filterable by status, and paginated by cursor. Clicking a row opens a grading\n * drawer that shows the learner's answers read-only (rendered safely from the\n * `answers` JSON blob) and — when the submission is awaiting grading — a grading\n * form (`useBuiGradeSubmission`: score, passed, feedback). Already-graded rows\n * show their result read-only with a \"Re-grade\" affordance.\n *\n * Every number is derived from live `bui*` data; the KPI row is explicitly scoped\n * to the current page (\"this page\") so it never overstates workspace totals.\n */\n\nimport { useMemo, useState } from 'react';\nimport type { FC, ReactNode } from 'react';\nimport { ClipboardList, Clock, CheckCircle2, Undo2, GraduationCap } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Switch,\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 FilterTabs,\n Pagination,\n EmptyState,\n LoadingView,\n ErrorView,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiSubmissions, useBuiGradeSubmission, useCursorPager } from '../../hooks';\nimport { formatDateTime } from '../../utils/formatters';\nimport type { BuiSubmission, BuiSubmissionStatus } from '../../types';\n\nconst PAGE_SIZE = 24;\n\ntype StatusFilter = BuiSubmissionStatus | 'ALL';\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SUBMITTED', label: 'Submitted' },\n { value: 'GRADING', label: 'Grading' },\n { value: 'GRADED', label: 'Graded' },\n { value: 'RETURNED', label: 'Returned' },\n];\n\n/** Truncate an opaque id for compact display, keeping the full value in `title`. */\nconst shortId = (id: string): string => (id.length > 10 ? `${id.slice(0, 8)}…` : id);\n\nconst isPlainRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\n/** Render a single answer value safely, whatever its JSON shape. */\nconst formatAnswerValue = (v: unknown): string => {\n if (v == null) return '—';\n if (typeof v === 'string') return v;\n if (typeof v === 'number' || typeof v === 'boolean') return String(v);\n return JSON.stringify(v, null, 2);\n};\n\n/** Read-only rendering of a submission's `answers` JSON blob. */\nconst AnswersView: FC<{ answers: unknown }> = ({ answers }) => {\n if (answers == null) {\n return <p className=\"text-sm text-text-secondary\">No answers were submitted.</p>;\n }\n if (isPlainRecord(answers)) {\n const entries = Object.entries(answers);\n if (entries.length === 0) {\n return <p className=\"text-sm text-text-secondary\">No answers were submitted.</p>;\n }\n return (\n <dl className=\"divide-y divide-border-seam/60\">\n {entries.map(([key, value]) => (\n <div key={key} className=\"grid gap-1 py-2 sm:grid-cols-3 sm:gap-3\">\n <dt className=\"break-words font-mono text-xs text-text-secondary sm:col-span-1\">\n {key}\n </dt>\n <dd className=\"whitespace-pre-wrap break-words text-sm text-text-primary sm:col-span-2\">\n {formatAnswerValue(value)}\n </dd>\n </div>\n ))}\n </dl>\n );\n }\n return (\n <pre className=\"overflow-x-auto whitespace-pre-wrap break-words text-xs text-text-primary\">\n {JSON.stringify(answers, null, 2)}\n </pre>\n );\n};\n\n/** A labelled read-only field for the submission meta / graded-result panels. */\nconst MetaField: FC<{ label: string; children: ReactNode }> = ({ label, children }) => (\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <div className=\"mt-0.5 text-sm text-text-primary\">{children}</div>\n </div>\n);\n\nexport const SubmissionsPage: FC = () => {\n const { currentUser } = useBuildmyiq();\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const pager = useCursorPager();\n\n const [selected, setSelected] = useState<BuiSubmission | null>(null);\n const [editMode, setEditMode] = useState(false);\n const [scoreInput, setScoreInput] = useState('');\n const [passed, setPassed] = useState(false);\n const [feedback, setFeedback] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useBuiSubmissions({\n status: statusFilter === 'ALL' ? undefined : statusFilter,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n const gradeSubmission = useBuiGradeSubmission();\n\n const rows = useMemo<BuiSubmission[]>(() => data?.items ?? [], [data]);\n\n // Counts are scoped to the loaded page — labelled \"this page\" to stay honest.\n const counts = useMemo(() => {\n const acc = { total: rows.length, awaiting: 0, graded: 0, returned: 0 };\n for (const s of rows) {\n if (s.status === 'SUBMITTED' || s.status === 'GRADING') acc.awaiting += 1;\n else if (s.status === 'GRADED') acc.graded += 1;\n else if (s.status === 'RETURNED') acc.returned += 1;\n }\n return acc;\n }, [rows]);\n\n const openGrade = (s: BuiSubmission) => {\n setSelected(s);\n setScoreInput(s.score != null ? String(s.score) : '');\n setPassed(s.passed ?? false);\n setFeedback(s.feedback ?? '');\n setEditMode(false);\n setFormError(null);\n };\n\n const closeModal = () => {\n setSelected(null);\n setEditMode(false);\n setFormError(null);\n };\n\n const handleFilterChange = (value: StatusFilter) => {\n setStatusFilter(value);\n pager.reset();\n };\n\n const handleGrade = async () => {\n if (!selected) return;\n const trimmed = scoreInput.trim();\n const scoreNum = Number(trimmed);\n if (trimmed === '' || Number.isNaN(scoreNum)) {\n setFormError('Enter a numeric score.');\n return;\n }\n if (scoreNum < 0) {\n setFormError('Score cannot be negative.');\n return;\n }\n try {\n await gradeSubmission.mutateAsync({\n id: selected.id,\n input: { score: scoreNum, passed, feedback: feedback.trim() || undefined },\n });\n closeModal();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to save grade.');\n }\n };\n\n const gradingAllowed =\n selected != null && (selected.status === 'SUBMITTED' || selected.status === 'GRADING');\n const alreadyGraded =\n selected != null && (selected.status === 'GRADED' || selected.status === 'RETURNED');\n const notSubmitted = selected != null && selected.status === 'IN_PROGRESS';\n const showGradeForm = selected != null && (gradingAllowed || editMode);\n\n const columns: Column<BuiSubmission>[] = [\n {\n key: 'learner',\n header: 'Learner',\n sortValue: (s) => s.learnerId,\n render: (s) =>\n s.learnerId === currentUser?.id ? (\n <span className=\"font-medium text-text-primary\">You</span>\n ) : (\n <span className=\"font-mono text-xs text-text-secondary\" title={s.learnerId}>\n {shortId(s.learnerId)}\n </span>\n ),\n },\n {\n key: 'assessment',\n header: 'Assessment',\n hideOnMobile: true,\n render: (s) => (\n <span className=\"font-mono text-xs text-text-secondary\" title={s.assessmentId}>\n {shortId(s.assessmentId)}\n </span>\n ),\n },\n {\n key: 'attempt',\n header: 'Attempt',\n align: 'right',\n hideOnMobile: true,\n sortValue: (s) => s.attempt,\n render: (s) => <span className=\"tabular-nums text-text-secondary\">#{s.attempt}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n sortValue: (s) => s.status,\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'score',\n header: 'Score',\n align: 'right',\n sortValue: (s) => s.score ?? -1,\n render: (s) => (\n <span className=\"tabular-nums font-medium text-text-primary\">{s.score ?? '—'}</span>\n ),\n },\n {\n key: 'submittedAt',\n header: 'Submitted',\n hideOnMobile: true,\n sortValue: (s) => s.submittedAt ?? s.createdAt,\n render: (s) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(s.submittedAt)}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Grading\"\n description=\"Review and grade assessment submissions across every course in your workspace.\"\n titleGradient\n aurora\n >\n <div className=\"flex flex-col gap-6\">\n {/* KPI row — scoped to the current page. */}\n <div className=\"grid grid-cols-2 gap-4 xl:grid-cols-4\">\n <MetricsCard\n icon={<ClipboardList className=\"h-5 w-5\" />}\n value={counts.total}\n label=\"Submissions (this page)\"\n featured\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={counts.awaiting}\n label=\"Awaiting grade (this page)\"\n accent=\"warning\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={counts.graded}\n label=\"Graded (this page)\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Undo2 className=\"h-5 w-5\" />}\n value={counts.returned}\n label=\"Returned (this page)\"\n />\n </div>\n\n <FilterTabs options={STATUS_FILTERS} value={statusFilter} onChange={handleFilterChange} />\n\n {isLoading ? (\n <LoadingView label=\"Loading submissions…\" />\n ) : isError ? (\n <ErrorView\n title=\"Couldn't load submissions\"\n message=\"The grading queue failed to load.\"\n onRetry={() => refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<GraduationCap className=\"h-10 w-10\" />}\n title={statusFilter === 'ALL' ? 'No submissions yet' : 'Nothing in this status'}\n description={\n statusFilter === 'ALL'\n ? 'When learners submit assessments, their attempts will appear here for grading.'\n : 'No submissions currently match this status filter.'\n }\n />\n ) : (\n <div>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n onRowClick={openGrade}\n defaultSort={{ key: 'submittedAt', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo?.cursor)}\n />\n </div>\n )}\n </div>\n\n <ResponsiveDialog\n open={selected != null}\n onOpenChange={(open) => {\n if (!open) closeModal();\n }}\n >\n <ResponsiveDialogContent className=\"max-h-[88vh] overflow-y-auto sm:max-w-[640px]\">\n {selected && (\n <>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {gradingAllowed || editMode ? 'Grade submission' : 'Submission details'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {selected.learnerId === currentUser?.id\n ? 'Your attempt'\n : `Learner ${shortId(selected.learnerId)}`}{' '}\n · attempt #{selected.attempt}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-5 py-2\">\n {/* Meta */}\n <div className=\"grid grid-cols-2 gap-4 rounded-xl bg-bg-sunken/60 p-4 sm:grid-cols-3\">\n <MetaField label=\"Status\">\n <StatusBadge status={selected.status} />\n </MetaField>\n <MetaField label=\"Attempt\">\n <span className=\"tabular-nums\">#{selected.attempt}</span>\n </MetaField>\n <MetaField label=\"Submitted\">{formatDateTime(selected.submittedAt)}</MetaField>\n <MetaField label=\"Assessment\">\n <span className=\"font-mono text-xs\" title={selected.assessmentId}>\n {shortId(selected.assessmentId)}\n </span>\n </MetaField>\n {selected.enrollmentId && (\n <MetaField label=\"Enrollment\">\n <span className=\"font-mono text-xs\" title={selected.enrollmentId}>\n {shortId(selected.enrollmentId)}\n </span>\n </MetaField>\n )}\n {selected.gradedAt && (\n <MetaField label=\"Graded\">{formatDateTime(selected.gradedAt)}</MetaField>\n )}\n </div>\n\n {/* Learner answers (read-only) */}\n <div>\n <p className=\"mb-2 text-sm font-semibold text-text-primary\">Answers</p>\n <div className=\"max-h-64 overflow-y-auto rounded-xl border border-border-seam p-4\">\n <AnswersView answers={selected.answers} />\n </div>\n </div>\n\n {/* Not-yet-submitted note */}\n {notSubmitted && (\n <p className=\"rounded-lg bg-bg-sunken/60 px-4 py-3 text-sm text-text-secondary\">\n This attempt is still in progress — there is nothing to grade until the learner\n submits.\n </p>\n )}\n\n {/* Existing grade (read-only) with a re-grade affordance */}\n {alreadyGraded && !editMode && (\n <div className=\"rounded-xl border border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"grid grid-cols-2 gap-4\">\n <MetaField label=\"Score\">\n <span className=\"tabular-nums font-semibold\">\n {selected.score ?? '—'}\n </span>\n </MetaField>\n <MetaField label=\"Result\">\n {selected.passed == null ? (\n <span className=\"text-text-secondary\">—</span>\n ) : (\n <StatusBadge status={selected.passed ? 'GRADED' : 'RETURNED'} />\n )}\n </MetaField>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditMode(true)}>\n Re-grade\n </Button>\n </div>\n <div className=\"mt-3\">\n <MetaField label=\"Feedback\">\n {selected.feedback ? (\n <span className=\"whitespace-pre-wrap\">{selected.feedback}</span>\n ) : (\n <span className=\"text-text-secondary\">No feedback provided.</span>\n )}\n </MetaField>\n </div>\n </div>\n )}\n\n {/* Grading form */}\n {showGradeForm && (\n <div className=\"grid gap-4\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade-score\">Score</Label>\n <Input\n id=\"grade-score\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={scoreInput}\n onChange={(e) => setScoreInput(e.target.value)}\n placeholder=\"e.g. 85\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade-passed\">Result</Label>\n <div className=\"flex h-9 items-center gap-3\">\n <Switch id=\"grade-passed\" checked={passed} onCheckedChange={setPassed} />\n <span className=\"text-sm text-text-secondary\">\n {passed ? 'Passed' : 'Not passed'}\n </span>\n </div>\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade-feedback\">Feedback</Label>\n <Textarea\n id=\"grade-feedback\"\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder=\"Notes, corrections or encouragement for the learner (optional).\"\n rows={3}\n />\n </div>\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeModal}>\n {showGradeForm ? 'Cancel' : 'Close'}\n </Button>\n {showGradeForm && (\n <Button onClick={handleGrade} disabled={gradeSubmission.isPending}>\n {gradeSubmission.isPending\n ? 'Saving…'\n : alreadyGraded\n ? 'Update grade'\n : 'Save grade'}\n </Button>\n )}\n </ResponsiveDialogFooter>\n </>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/buildmyiq/types/index.ts"],"sourcesContent":["/**\n * Buildmyiq Microfrontend — TypeScript Type Definitions\n *\n * Buildmyiq — the AI-native education, learning & development system of record.\n * Domain pillars, each 1:1 with a `wspace-buildmyiq-svc` capability:\n * - Catalog & authoring: courses → modules → lessons, skills taxonomy\n * - Assessment: assessments → questions → submissions → grading\n * - Enrolment & progress: enrollments, lesson progress, cohorts\n * - Credentialing: certificates\n *\n * All server-facing types below mirror the `wspace-buildmyiq-svc` federated\n * GraphQL contract exactly (the `Bui*` types, `bui*` root fields). No mock data.\n *\n * @module buildmyiq/types\n */\n\nimport type { ReactNode } from 'react';\n\n// ============================================================================\n// Shell Integration Types\n// ============================================================================\n\nexport interface NavItem {\n id: string;\n label: string;\n path: string;\n icon?: ReactNode;\n badge?: number;\n order?: number;\n group?: string;\n visible?: boolean;\n external?: boolean;\n children?: NavItem[];\n}\n\nexport interface FooterItem {\n id: string;\n label: string;\n icon?: string;\n onClick?: () => void;\n type?: 'status' | 'action' | 'info';\n order?: number;\n badge?: string;\n section?: 'left' | 'center' | 'right';\n visible?: boolean;\n}\n\n/**\n * Props for the BuildmyiqRoot component. Host-contract preserving: the app\n * shell spreads `workspaceBaseProps` (basePath, currentUser, workspaceId,\n * tenantId, apiGatewayUrl, authToken, navigate, registerNavItems,\n * registerFooterItems) onto this component.\n */\nexport interface BuildmyiqRootProps {\n basePath?: string;\n workspaceId?: string;\n tenantId?: string;\n apiGatewayUrl?: string;\n authToken?: string;\n navigate?: (path: string) => void;\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n username?: string;\n name?: string;\n role?: string;\n } | null;\n registerNavItems?: (items: NavItem | NavItem[]) => () => void;\n registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;\n}\n\nexport type TrendDirection = 'up' | 'down' | 'neutral';\n\n// ============================================================================\n// Pagination (mirror svc PaginationInput / Bui*PageInfo)\n// ============================================================================\n\n/**\n * The svc `PaginationInput` accepts { limit, offset, cursor } but the resolver\n * uses cursor+limit only (offset is a no-op). We drive pages by cursor:\n * pass `pagination.cursor = previous pageInfo.cursor` for the next page. The\n * FIRST page must OMIT cursor entirely (an explicit `cursor: null` is rejected).\n */\nexport interface PaginationInput {\n limit?: number;\n offset?: number;\n cursor?: string | null;\n}\n\n/** All Bui*PageInfo variants are structurally { cursor, hasMore }. */\nexport interface BuiPageInfo {\n cursor?: string | null;\n hasMore: boolean;\n}\n\nexport interface BuiConnection<T> {\n items: T[];\n pageInfo: BuiPageInfo;\n}\n\n// ============================================================================\n// Federated stubs (owned by other subgraphs — wspace-files-svc)\n// ============================================================================\n\n/** A wspace-files-svc File reference resolved for cover/media/certificate. */\nexport interface BuiFileRef {\n id: string;\n name?: string | null;\n url?: string | null;\n mimeType?: string | null;\n sizeBytes?: number | null;\n}\n\n// ============================================================================\n// Domain enums (mirror wspace-buildmyiq-svc .hive-schema.graphql exactly)\n// ============================================================================\n\nexport type BuiSkillCategory =\n | 'TECHNICAL'\n | 'BUSINESS'\n | 'CREATIVE'\n | 'COGNITIVE'\n | 'SOCIAL'\n | 'LANGUAGE';\n\nexport type BuiCourseLevel = 'BEGINNER' | 'INTERMEDIATE' | 'ADVANCED' | 'EXPERT';\n\nexport type BuiCourseStatus = 'DRAFT' | 'IN_REVIEW' | 'PUBLISHED' | 'ARCHIVED';\n\nexport type BuiLessonType =\n | 'ARTICLE'\n | 'VIDEO'\n | 'QUIZ'\n | 'ASSIGNMENT'\n | 'INTERACTIVE'\n | 'LIVE_SESSION';\n\nexport type BuiAssessmentType = 'QUIZ' | 'EXAM' | 'ASSIGNMENT' | 'PRACTICAL' | 'SURVEY';\n\nexport type BuiAssessmentStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';\n\nexport type BuiQuestionType =\n | 'SINGLE_CHOICE'\n | 'MULTIPLE_CHOICE'\n | 'TRUE_FALSE'\n | 'SHORT_ANSWER'\n | 'ESSAY'\n | 'CODE';\n\nexport type BuiSubmissionStatus = 'IN_PROGRESS' | 'SUBMITTED' | 'GRADING' | 'GRADED' | 'RETURNED';\n\nexport type BuiEnrollmentStatus =\n | 'ENROLLED'\n | 'IN_PROGRESS'\n | 'COMPLETED'\n | 'DROPPED'\n | 'EXPIRED';\n\nexport type BuiProgressStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'COMPLETED';\n\nexport type BuiCohortStatus = 'SCHEDULED' | 'ENROLLING' | 'ACTIVE' | 'COMPLETED' | 'CANCELLED';\n\nexport type BuiCertificateStatus = 'ISSUED' | 'EXPIRED' | 'REVOKED';\n\n// ============================================================================\n// Domain entity types (mirror the `Bui*` GraphQL types exactly)\n// ============================================================================\n\nexport interface BuiSkill {\n id: string;\n workspaceId: string;\n name: string;\n category: BuiSkillCategory;\n description?: string | null;\n isActive: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiModule {\n id: string;\n workspaceId: string;\n courseId: string;\n title: string;\n description?: string | null;\n position: number;\n lessons: BuiLesson[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiLesson {\n id: string;\n workspaceId: string;\n courseId: string;\n moduleId?: string | null;\n title: string;\n type: BuiLessonType;\n content?: string | null;\n media?: BuiFileRef | null;\n durationMin: number;\n position: number;\n isPreview: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiCourse {\n id: string;\n workspaceId: string;\n title: string;\n slug: string;\n summary?: string | null;\n description?: string | null;\n status: BuiCourseStatus;\n level: BuiCourseLevel;\n language: string;\n estimatedHours: number;\n isPublic: boolean;\n tags: string[];\n skillId?: string | null;\n skill?: BuiSkill | null;\n instructorId?: string | null;\n cover?: BuiFileRef | null;\n publishedAt?: string | null;\n modules: BuiModule[];\n lessons: BuiLesson[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiQuestion {\n id: string;\n workspaceId: string;\n assessmentId: string;\n prompt: string;\n type: BuiQuestionType;\n /**\n * JSON blob — array of `{ id?, label }` choices for choice questions. NOTE:\n * `answerKey` is a CREATE-INPUT field only; it is NOT exposed on the type\n * (never query it in a fragment — GRAPHQL_VALIDATION_FAILED).\n */\n options?: unknown;\n points: number;\n position: number;\n explanation?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiAssessment {\n id: string;\n workspaceId: string;\n courseId: string;\n title: string;\n description?: string | null;\n type: BuiAssessmentType;\n status: BuiAssessmentStatus;\n passingScore: number;\n maxAttempts: number;\n timeLimitMin: number;\n isGraded: boolean;\n questions: BuiQuestion[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiSubmission {\n id: string;\n workspaceId: string;\n assessmentId: string;\n enrollmentId?: string | null;\n learnerId: string;\n attempt: number;\n /** JSON blob — learner answers keyed by questionId. */\n answers?: unknown;\n status: BuiSubmissionStatus;\n score?: number | null;\n passed?: boolean | null;\n feedback?: string | null;\n gradedById?: string | null;\n gradedAt?: string | null;\n submittedAt?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiLessonProgress {\n id: string;\n workspaceId: string;\n enrollmentId: string;\n lessonId: string;\n status: BuiProgressStatus;\n secondsSpent: number;\n completedAt?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiCohort {\n id: string;\n workspaceId: string;\n courseId: string;\n name: string;\n capacity: number;\n facilitatorId?: string | null;\n startDate?: string | null;\n endDate?: string | null;\n status: BuiCohortStatus;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiEnrollment {\n id: string;\n workspaceId: string;\n courseId: string;\n learnerId: string;\n cohortId?: string | null;\n cohort?: BuiCohort | null;\n status: BuiEnrollmentStatus;\n progressPct: number;\n enrolledAt: string;\n startedAt?: string | null;\n completedAt?: string | null;\n lastActiveAt?: string | null;\n expiresAt?: string | null;\n lessonProgress: BuiLessonProgress[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiCertificate {\n id: string;\n workspaceId: string;\n courseId: string;\n learnerId: string;\n enrollmentId?: string | null;\n serial: string;\n status: BuiCertificateStatus;\n issuedAt: string;\n expiresAt?: string | null;\n revokedAt?: string | null;\n finalScore?: number | null;\n file?: BuiFileRef | null;\n createdAt: string;\n updatedAt: string;\n}\n\n// ============================================================================\n// Mutation input types (mirror the svc `Bui*Input` shapes)\n// ============================================================================\n\nexport interface BuiCreateCourseInput {\n title: string;\n slug: string;\n summary?: string;\n description?: string;\n level?: BuiCourseLevel;\n language?: string;\n estimatedHours?: number;\n isPublic?: boolean;\n tags?: string[];\n skillId?: string;\n instructorId?: string;\n coverFileId?: string;\n}\n\nexport interface BuiUpdateCourseInput {\n title?: string;\n summary?: string;\n description?: string;\n level?: BuiCourseLevel;\n language?: string;\n estimatedHours?: number;\n isPublic?: boolean;\n tags?: string[];\n skillId?: string;\n instructorId?: string;\n coverFileId?: string;\n}\n\nexport interface BuiCreateModuleInput {\n courseId: string;\n title: string;\n description?: string;\n position?: number;\n}\n\nexport interface BuiCreateLessonInput {\n courseId: string;\n moduleId?: string;\n title: string;\n type?: BuiLessonType;\n content?: string;\n durationMin?: number;\n position?: number;\n isPreview?: boolean;\n mediaFileId?: string;\n}\n\nexport interface BuiCreateAssessmentInput {\n courseId: string;\n title: string;\n description?: string;\n type?: BuiAssessmentType;\n passingScore?: number;\n maxAttempts?: number;\n timeLimitMin?: number;\n isGraded?: boolean;\n}\n\nexport interface BuiCreateQuestionInput {\n assessmentId: string;\n prompt: string;\n type?: BuiQuestionType;\n options?: unknown;\n answerKey?: unknown;\n points?: number;\n position?: number;\n explanation?: string;\n}\n\nexport interface BuiCreateSkillInput {\n name: string;\n category?: BuiSkillCategory;\n description?: string;\n}\n\nexport interface BuiCreateCohortInput {\n courseId: string;\n name: string;\n capacity?: number;\n facilitatorId?: string;\n startDate?: string;\n endDate?: string;\n}\n\nexport interface BuiEnrollInput {\n courseId: string;\n learnerId?: string;\n cohortId?: string;\n expiresAt?: string;\n}\n\nexport interface BuiRecordLessonProgressInput {\n enrollmentId: string;\n lessonId: string;\n secondsSpent?: number;\n status?: BuiProgressStatus;\n}\n\nexport interface BuiSubmitAssessmentInput {\n assessmentId: string;\n learnerId?: string;\n enrollmentId?: string;\n attempt?: number;\n answers?: unknown;\n}\n\nexport interface BuiGradeSubmissionInput {\n score: number;\n passed?: boolean;\n feedback?: string;\n}\n\nexport interface BuiIssueCertificateInput {\n courseId: string;\n learnerId: string;\n enrollmentId?: string;\n serial: string;\n finalScore?: number;\n expiresAt?: string;\n fileId?: string;\n}\n\n// ============================================================================\n// Convenience option lists (for select controls in authoring UIs)\n// ============================================================================\n\nexport const COURSE_LEVELS: BuiCourseLevel[] = ['BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'EXPERT'];\nexport const COURSE_STATUSES: BuiCourseStatus[] = ['DRAFT', 'IN_REVIEW', 'PUBLISHED', 'ARCHIVED'];\nexport const LESSON_TYPES: BuiLessonType[] = [\n 'ARTICLE',\n 'VIDEO',\n 'QUIZ',\n 'ASSIGNMENT',\n 'INTERACTIVE',\n 'LIVE_SESSION',\n];\nexport const ASSESSMENT_TYPES: BuiAssessmentType[] = [\n 'QUIZ',\n 'EXAM',\n 'ASSIGNMENT',\n 'PRACTICAL',\n 'SURVEY',\n];\nexport const QUESTION_TYPES: BuiQuestionType[] = [\n 'SINGLE_CHOICE',\n 'MULTIPLE_CHOICE',\n 'TRUE_FALSE',\n 'SHORT_ANSWER',\n 'ESSAY',\n 'CODE',\n];\nexport const SKILL_CATEGORIES: BuiSkillCategory[] = [\n 'TECHNICAL',\n 'BUSINESS',\n 'CREATIVE',\n 'COGNITIVE',\n 'SOCIAL',\n 'LANGUAGE',\n];\nexport const ENROLLMENT_STATUSES: BuiEnrollmentStatus[] = [\n 'ENROLLED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DROPPED',\n 'EXPIRED',\n];\nexport const COHORT_STATUSES: BuiCohortStatus[] = [\n 'SCHEDULED',\n 'ENROLLING',\n 'ACTIVE',\n 'COMPLETED',\n 'CANCELLED',\n];\nexport const SUBMISSION_STATUSES: BuiSubmissionStatus[] = [\n 'IN_PROGRESS',\n 'SUBMITTED',\n 'GRADING',\n 'GRADED',\n 'RETURNED',\n];\n"],"mappings":";AAmeA,IAAa,IAAkC;CAAC;CAAY;CAAgB;CAAY;CAAS,EAEpF,IAAgC;CAC3C;CACA;CACA;CACA;CACA;CACA;CACD,EACY,IAAwC;CACnD;CACA;CACA;CACA;CACA;CACD,EACY,IAAoC;CAC/C;CACA;CACA;CACA;CACA;CACA;CACD,EACY,IAAuC;CAClD;CACA;CACA;CACA;CACA;CACA;CACD,EAQY,IAAqC;CAChD;CACA;CACA;CACA;CACA;CACD"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/buildmyiq/types/index.ts"],"sourcesContent":["/**\n * Buildmyiq Microfrontend — TypeScript Type Definitions\n *\n * Buildmyiq — the AI-native education, learning & development system of record.\n * Domain pillars, each 1:1 with a `wspace-buildmyiq-svc` capability:\n * - Catalog & authoring: courses → modules → lessons, skills taxonomy\n * - Assessment: assessments → questions → submissions → grading\n * - Enrolment & progress: enrollments, lesson progress, cohorts\n * - Credentialing: certificates\n *\n * All server-facing types below mirror the `wspace-buildmyiq-svc` federated\n * GraphQL contract exactly (the `Bui*` types, `bui*` root fields). No mock data.\n *\n * @module buildmyiq/types\n */\n\nimport type { ReactNode } from 'react';\n\n// ============================================================================\n// Shell Integration Types\n// ============================================================================\n\nexport interface NavItem {\n id: string;\n label: string;\n path: string;\n icon?: ReactNode;\n badge?: number;\n order?: number;\n group?: string;\n visible?: boolean;\n external?: boolean;\n children?: NavItem[];\n}\n\nexport interface FooterItem {\n id: string;\n label: string;\n icon?: string;\n onClick?: () => void;\n type?: 'status' | 'action' | 'info';\n order?: number;\n badge?: string;\n section?: 'left' | 'center' | 'right';\n visible?: boolean;\n}\n\n/**\n * Props for the BuildmyiqRoot component. Host-contract preserving: the app\n * shell spreads `workspaceBaseProps` (basePath, currentUser, workspaceId,\n * tenantId, apiGatewayUrl, authToken, navigate, registerNavItems,\n * registerFooterItems) onto this component.\n */\nexport interface BuildmyiqRootProps {\n basePath?: string;\n workspaceId?: string;\n tenantId?: string;\n apiGatewayUrl?: string;\n authToken?: string;\n navigate?: (path: string) => void;\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n username?: string;\n name?: string;\n role?: string;\n } | null;\n registerNavItems?: (items: NavItem | NavItem[]) => () => void;\n registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;\n}\n\nexport type TrendDirection = 'up' | 'down' | 'neutral';\n\n// ============================================================================\n// Pagination (mirror svc PaginationInput / Bui*PageInfo)\n// ============================================================================\n\n/**\n * The svc `PaginationInput` accepts { limit, offset, cursor } but the resolver\n * uses cursor+limit only (offset is a no-op). We drive pages by cursor:\n * pass `pagination.cursor = previous pageInfo.cursor` for the next page. The\n * FIRST page must OMIT cursor entirely (an explicit `cursor: null` is rejected).\n */\nexport interface PaginationInput {\n limit?: number;\n offset?: number;\n cursor?: string | null;\n}\n\n/** All Bui*PageInfo variants are structurally { cursor, hasMore }. */\nexport interface BuiPageInfo {\n cursor?: string | null;\n hasMore: boolean;\n}\n\nexport interface BuiConnection<T> {\n items: T[];\n pageInfo: BuiPageInfo;\n}\n\n// ============================================================================\n// Federated stubs (owned by other subgraphs — wspace-files-svc)\n// ============================================================================\n\n/** A wspace-files-svc File reference resolved for cover/media/certificate. */\nexport interface BuiFileRef {\n id: string;\n name?: string | null;\n url?: string | null;\n mimeType?: string | null;\n sizeBytes?: number | null;\n}\n\n// ============================================================================\n// Domain enums (mirror wspace-buildmyiq-svc .hive-schema.graphql exactly)\n// ============================================================================\n\nexport type BuiSkillCategory =\n | 'TECHNICAL'\n | 'BUSINESS'\n | 'CREATIVE'\n | 'COGNITIVE'\n | 'SOCIAL'\n | 'LANGUAGE';\n\nexport type BuiCourseLevel = 'BEGINNER' | 'INTERMEDIATE' | 'ADVANCED' | 'EXPERT';\n\nexport type BuiCourseStatus = 'DRAFT' | 'IN_REVIEW' | 'PUBLISHED' | 'ARCHIVED';\n\nexport type BuiLessonType =\n | 'ARTICLE'\n | 'VIDEO'\n | 'QUIZ'\n | 'ASSIGNMENT'\n | 'INTERACTIVE'\n | 'LIVE_SESSION';\n\nexport type BuiAssessmentType = 'QUIZ' | 'EXAM' | 'ASSIGNMENT' | 'PRACTICAL' | 'SURVEY';\n\nexport type BuiAssessmentStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';\n\nexport type BuiQuestionType =\n | 'SINGLE_CHOICE'\n | 'MULTIPLE_CHOICE'\n | 'TRUE_FALSE'\n | 'SHORT_ANSWER'\n | 'ESSAY'\n | 'CODE';\n\nexport type BuiSubmissionStatus = 'IN_PROGRESS' | 'SUBMITTED' | 'GRADING' | 'GRADED' | 'RETURNED';\n\nexport type BuiEnrollmentStatus = 'ENROLLED' | 'IN_PROGRESS' | 'COMPLETED' | 'DROPPED' | 'EXPIRED';\n\nexport type BuiProgressStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'COMPLETED';\n\nexport type BuiCohortStatus = 'SCHEDULED' | 'ENROLLING' | 'ACTIVE' | 'COMPLETED' | 'CANCELLED';\n\nexport type BuiCertificateStatus = 'ISSUED' | 'EXPIRED' | 'REVOKED';\n\n// ============================================================================\n// Domain entity types (mirror the `Bui*` GraphQL types exactly)\n// ============================================================================\n\nexport interface BuiSkill {\n id: string;\n workspaceId: string;\n name: string;\n category: BuiSkillCategory;\n description?: string | null;\n isActive: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiModule {\n id: string;\n workspaceId: string;\n courseId: string;\n title: string;\n description?: string | null;\n position: number;\n lessons: BuiLesson[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiLesson {\n id: string;\n workspaceId: string;\n courseId: string;\n moduleId?: string | null;\n title: string;\n type: BuiLessonType;\n content?: string | null;\n media?: BuiFileRef | null;\n durationMin: number;\n position: number;\n isPreview: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiCourse {\n id: string;\n workspaceId: string;\n title: string;\n slug: string;\n summary?: string | null;\n description?: string | null;\n status: BuiCourseStatus;\n level: BuiCourseLevel;\n language: string;\n estimatedHours: number;\n isPublic: boolean;\n tags: string[];\n skillId?: string | null;\n skill?: BuiSkill | null;\n instructorId?: string | null;\n cover?: BuiFileRef | null;\n publishedAt?: string | null;\n modules: BuiModule[];\n lessons: BuiLesson[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiQuestion {\n id: string;\n workspaceId: string;\n assessmentId: string;\n prompt: string;\n type: BuiQuestionType;\n /**\n * JSON blob — array of `{ id?, label }` choices for choice questions. NOTE:\n * `answerKey` is a CREATE-INPUT field only; it is NOT exposed on the type\n * (never query it in a fragment — GRAPHQL_VALIDATION_FAILED).\n */\n options?: unknown;\n points: number;\n position: number;\n explanation?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiAssessment {\n id: string;\n workspaceId: string;\n courseId: string;\n title: string;\n description?: string | null;\n type: BuiAssessmentType;\n status: BuiAssessmentStatus;\n passingScore: number;\n maxAttempts: number;\n timeLimitMin: number;\n isGraded: boolean;\n questions: BuiQuestion[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiSubmission {\n id: string;\n workspaceId: string;\n assessmentId: string;\n enrollmentId?: string | null;\n learnerId: string;\n attempt: number;\n /** JSON blob — learner answers keyed by questionId. */\n answers?: unknown;\n status: BuiSubmissionStatus;\n score?: number | null;\n passed?: boolean | null;\n feedback?: string | null;\n gradedById?: string | null;\n gradedAt?: string | null;\n submittedAt?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiLessonProgress {\n id: string;\n workspaceId: string;\n enrollmentId: string;\n lessonId: string;\n status: BuiProgressStatus;\n secondsSpent: number;\n completedAt?: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiCohort {\n id: string;\n workspaceId: string;\n courseId: string;\n name: string;\n capacity: number;\n facilitatorId?: string | null;\n startDate?: string | null;\n endDate?: string | null;\n status: BuiCohortStatus;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiEnrollment {\n id: string;\n workspaceId: string;\n courseId: string;\n learnerId: string;\n cohortId?: string | null;\n cohort?: BuiCohort | null;\n status: BuiEnrollmentStatus;\n progressPct: number;\n enrolledAt: string;\n startedAt?: string | null;\n completedAt?: string | null;\n lastActiveAt?: string | null;\n expiresAt?: string | null;\n lessonProgress: BuiLessonProgress[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface BuiCertificate {\n id: string;\n workspaceId: string;\n courseId: string;\n learnerId: string;\n enrollmentId?: string | null;\n serial: string;\n status: BuiCertificateStatus;\n issuedAt: string;\n expiresAt?: string | null;\n revokedAt?: string | null;\n finalScore?: number | null;\n file?: BuiFileRef | null;\n createdAt: string;\n updatedAt: string;\n}\n\n// ============================================================================\n// Mutation input types (mirror the svc `Bui*Input` shapes)\n// ============================================================================\n\nexport interface BuiCreateCourseInput {\n title: string;\n slug: string;\n summary?: string;\n description?: string;\n level?: BuiCourseLevel;\n language?: string;\n estimatedHours?: number;\n isPublic?: boolean;\n tags?: string[];\n skillId?: string;\n instructorId?: string;\n coverFileId?: string;\n}\n\nexport interface BuiUpdateCourseInput {\n title?: string;\n summary?: string;\n description?: string;\n level?: BuiCourseLevel;\n language?: string;\n estimatedHours?: number;\n isPublic?: boolean;\n tags?: string[];\n skillId?: string;\n instructorId?: string;\n coverFileId?: string;\n}\n\nexport interface BuiCreateModuleInput {\n courseId: string;\n title: string;\n description?: string;\n position?: number;\n}\n\nexport interface BuiCreateLessonInput {\n courseId: string;\n moduleId?: string;\n title: string;\n type?: BuiLessonType;\n content?: string;\n durationMin?: number;\n position?: number;\n isPreview?: boolean;\n mediaFileId?: string;\n}\n\nexport interface BuiCreateAssessmentInput {\n courseId: string;\n title: string;\n description?: string;\n type?: BuiAssessmentType;\n passingScore?: number;\n maxAttempts?: number;\n timeLimitMin?: number;\n isGraded?: boolean;\n}\n\nexport interface BuiCreateQuestionInput {\n assessmentId: string;\n prompt: string;\n type?: BuiQuestionType;\n options?: unknown;\n answerKey?: unknown;\n points?: number;\n position?: number;\n explanation?: string;\n}\n\nexport interface BuiCreateSkillInput {\n name: string;\n category?: BuiSkillCategory;\n description?: string;\n}\n\nexport interface BuiCreateCohortInput {\n courseId: string;\n name: string;\n capacity?: number;\n facilitatorId?: string;\n startDate?: string;\n endDate?: string;\n}\n\nexport interface BuiEnrollInput {\n courseId: string;\n learnerId?: string;\n cohortId?: string;\n expiresAt?: string;\n}\n\nexport interface BuiRecordLessonProgressInput {\n enrollmentId: string;\n lessonId: string;\n secondsSpent?: number;\n status?: BuiProgressStatus;\n}\n\nexport interface BuiSubmitAssessmentInput {\n assessmentId: string;\n learnerId?: string;\n enrollmentId?: string;\n attempt?: number;\n answers?: unknown;\n}\n\nexport interface BuiGradeSubmissionInput {\n score: number;\n passed?: boolean;\n feedback?: string;\n}\n\nexport interface BuiIssueCertificateInput {\n courseId: string;\n learnerId: string;\n enrollmentId?: string;\n serial: string;\n finalScore?: number;\n expiresAt?: string;\n fileId?: string;\n}\n\n// ============================================================================\n// Convenience option lists (for select controls in authoring UIs)\n// ============================================================================\n\nexport const COURSE_LEVELS: BuiCourseLevel[] = ['BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'EXPERT'];\nexport const COURSE_STATUSES: BuiCourseStatus[] = ['DRAFT', 'IN_REVIEW', 'PUBLISHED', 'ARCHIVED'];\nexport const LESSON_TYPES: BuiLessonType[] = [\n 'ARTICLE',\n 'VIDEO',\n 'QUIZ',\n 'ASSIGNMENT',\n 'INTERACTIVE',\n 'LIVE_SESSION',\n];\nexport const ASSESSMENT_TYPES: BuiAssessmentType[] = [\n 'QUIZ',\n 'EXAM',\n 'ASSIGNMENT',\n 'PRACTICAL',\n 'SURVEY',\n];\nexport const QUESTION_TYPES: BuiQuestionType[] = [\n 'SINGLE_CHOICE',\n 'MULTIPLE_CHOICE',\n 'TRUE_FALSE',\n 'SHORT_ANSWER',\n 'ESSAY',\n 'CODE',\n];\nexport const SKILL_CATEGORIES: BuiSkillCategory[] = [\n 'TECHNICAL',\n 'BUSINESS',\n 'CREATIVE',\n 'COGNITIVE',\n 'SOCIAL',\n 'LANGUAGE',\n];\nexport const ENROLLMENT_STATUSES: BuiEnrollmentStatus[] = [\n 'ENROLLED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DROPPED',\n 'EXPIRED',\n];\nexport const COHORT_STATUSES: BuiCohortStatus[] = [\n 'SCHEDULED',\n 'ENROLLING',\n 'ACTIVE',\n 'COMPLETED',\n 'CANCELLED',\n];\nexport const SUBMISSION_STATUSES: BuiSubmissionStatus[] = [\n 'IN_PROGRESS',\n 'SUBMITTED',\n 'GRADING',\n 'GRADED',\n 'RETURNED',\n];\n"],"mappings":";AA8dA,IAAa,IAAkC;CAAC;CAAY;CAAgB;CAAY;CAAS,EAEpF,IAAgC;CAC3C;CACA;CACA;CACA;CACA;CACA;CACD,EACY,IAAwC;CACnD;CACA;CACA;CACA;CACA;CACD,EACY,IAAoC;CAC/C;CACA;CACA;CACA;CACA;CACA;CACD,EACY,IAAuC;CAClD;CACA;CACA;CACA;CACA;CACA;CACD,EAQY,IAAqC;CAChD;CACA;CACA;CACA;CACA;CACD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../../node_modules
|
|
2
|
-
import { twMerge as t } from "../../node_modules
|
|
1
|
+
import e from "../../node_modules/clsx/dist/clsx.js";
|
|
2
|
+
import { twMerge as t } from "../../node_modules/tailwind-merge/dist/bundle-mjs.js";
|
|
3
3
|
//#region src/buildmyiq/utils/cn.ts
|
|
4
4
|
function n(...n) {
|
|
5
5
|
return t(e(n));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.js","names":[],"sources":["../../../src/buildmyiq/utils/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for the Buildmyiq microfrontend\n * @module buildmyiq/utils/formatters\n */\n\n/** Format a date string or Date into a human-readable date. */\nexport function formatDate(\n date: string | Date | null | undefined,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n ...options,\n }).format(d);\n}\n\n/** Format a date-time into a date + time string. */\nexport function formatDateTime(\n date: string | Date | null | undefined,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n ...options,\n }).format(d);\n}\n\n/** Format a date into a relative time string (e.g. \"2 hours ago\", \"in 3 days\"). */\nexport function formatRelativeTime(date: string | Date | null | undefined): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n\n const now = new Date();\n const diffMs = d.getTime() - now.getTime();\n const future = diffMs >= 0;\n const absSeconds = Math.floor(Math.abs(diffMs) / 1000);\n const minutes = Math.floor(absSeconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30);\n const years = Math.floor(days / 365);\n\n const phrase = (value: number, unit: string): string => {\n const label = `${value} ${unit}${value !== 1 ? 's' : ''}`;\n return future ? `in ${label}` : `${label} ago`;\n };\n\n if (absSeconds < 60) return future ? 'soon' : 'just now';\n if (minutes < 60) return phrase(minutes, 'min');\n if (hours < 24) return phrase(hours, 'hour');\n if (days < 7) return phrase(days, 'day');\n if (weeks < 4) return phrase(weeks, 'week');\n if (months < 12) return phrase(months, 'month');\n return phrase(years, 'year');\n}\n\n/** Format a number with locale grouping. */\nexport function formatNumber(\n value: number | null | undefined,\n options?: Partial<Intl.NumberFormatOptions>\n): string {\n if (value == null || Number.isNaN(value)) return '—';\n return new Intl.NumberFormat(undefined, options).format(value);\n}\n\n/** Format a quantity with an optional unit suffix (e.g. \"1,200 XP\"). */\nexport function formatQuantity(value: number | null | undefined, unit?: string | null): string {\n if (value == null || Number.isNaN(value)) return '—';\n const num = formatNumber(value, { maximumFractionDigits: 2 });\n return unit ? `${num} ${unit}` : num;\n}\n\n/** Format a percentage (0–100) into a rounded \"%\" string. */\nexport function formatPercent(value: number | null | undefined, fractionDigits = 0): string {\n if (value == null || Number.isNaN(value)) return '—';\n return `${value.toFixed(fractionDigits)}%`;\n}\n\n/**\n * Format a duration in minutes into a compact \"1h 30m\" / \"45m\" / \"2h\" string —\n * used for lesson duration and course estimated hours (× 60).\n */\nexport function formatDuration(minutes: number | null | undefined): string {\n if (minutes == null || Number.isNaN(minutes) || minutes <= 0) return '—';\n const h = Math.floor(minutes / 60);\n const m = Math.round(minutes % 60);\n if (h === 0) return `${m}m`;\n if (m === 0) return `${h}h`;\n return `${h}h ${m}m`;\n}\n\n/** Format a number of hours into a \"12 hrs\" / \"1 hr\" label. */\nexport function formatHours(hours: number | null | undefined): string {\n if (hours == null || Number.isNaN(hours)) return '—';\n return `${formatNumber(hours)} ${hours === 1 ? 'hr' : 'hrs'}`;\n}\n\n/** Turn an ENUM_LIKE_TOKEN into a human \"Enum Like Token\" label. */\nexport function formatEnumLabel(value: string | null | undefined): string {\n if (!value) return '—';\n return value\n .toLowerCase()\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n/** A short display name for a person from a partial identity object. */\nexport function formatPersonName(\n person:\n | {
|
|
1
|
+
{"version":3,"file":"formatters.js","names":[],"sources":["../../../src/buildmyiq/utils/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for the Buildmyiq microfrontend\n * @module buildmyiq/utils/formatters\n */\n\n/** Format a date string or Date into a human-readable date. */\nexport function formatDate(\n date: string | Date | null | undefined,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n ...options,\n }).format(d);\n}\n\n/** Format a date-time into a date + time string. */\nexport function formatDateTime(\n date: string | Date | null | undefined,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n ...options,\n }).format(d);\n}\n\n/** Format a date into a relative time string (e.g. \"2 hours ago\", \"in 3 days\"). */\nexport function formatRelativeTime(date: string | Date | null | undefined): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n\n const now = new Date();\n const diffMs = d.getTime() - now.getTime();\n const future = diffMs >= 0;\n const absSeconds = Math.floor(Math.abs(diffMs) / 1000);\n const minutes = Math.floor(absSeconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30);\n const years = Math.floor(days / 365);\n\n const phrase = (value: number, unit: string): string => {\n const label = `${value} ${unit}${value !== 1 ? 's' : ''}`;\n return future ? `in ${label}` : `${label} ago`;\n };\n\n if (absSeconds < 60) return future ? 'soon' : 'just now';\n if (minutes < 60) return phrase(minutes, 'min');\n if (hours < 24) return phrase(hours, 'hour');\n if (days < 7) return phrase(days, 'day');\n if (weeks < 4) return phrase(weeks, 'week');\n if (months < 12) return phrase(months, 'month');\n return phrase(years, 'year');\n}\n\n/** Format a number with locale grouping. */\nexport function formatNumber(\n value: number | null | undefined,\n options?: Partial<Intl.NumberFormatOptions>\n): string {\n if (value == null || Number.isNaN(value)) return '—';\n return new Intl.NumberFormat(undefined, options).format(value);\n}\n\n/** Format a quantity with an optional unit suffix (e.g. \"1,200 XP\"). */\nexport function formatQuantity(value: number | null | undefined, unit?: string | null): string {\n if (value == null || Number.isNaN(value)) return '—';\n const num = formatNumber(value, { maximumFractionDigits: 2 });\n return unit ? `${num} ${unit}` : num;\n}\n\n/** Format a percentage (0–100) into a rounded \"%\" string. */\nexport function formatPercent(value: number | null | undefined, fractionDigits = 0): string {\n if (value == null || Number.isNaN(value)) return '—';\n return `${value.toFixed(fractionDigits)}%`;\n}\n\n/**\n * Format a duration in minutes into a compact \"1h 30m\" / \"45m\" / \"2h\" string —\n * used for lesson duration and course estimated hours (× 60).\n */\nexport function formatDuration(minutes: number | null | undefined): string {\n if (minutes == null || Number.isNaN(minutes) || minutes <= 0) return '—';\n const h = Math.floor(minutes / 60);\n const m = Math.round(minutes % 60);\n if (h === 0) return `${m}m`;\n if (m === 0) return `${h}h`;\n return `${h}h ${m}m`;\n}\n\n/** Format a number of hours into a \"12 hrs\" / \"1 hr\" label. */\nexport function formatHours(hours: number | null | undefined): string {\n if (hours == null || Number.isNaN(hours)) return '—';\n return `${formatNumber(hours)} ${hours === 1 ? 'hr' : 'hrs'}`;\n}\n\n/** Turn an ENUM_LIKE_TOKEN into a human \"Enum Like Token\" label. */\nexport function formatEnumLabel(value: string | null | undefined): string {\n if (!value) return '—';\n return value\n .toLowerCase()\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n/** A short display name for a person from a partial identity object. */\nexport function formatPersonName(\n person:\n | {\n firstName?: string | null;\n lastName?: string | null;\n name?: string | null;\n email?: string | null;\n }\n | null\n | undefined\n): string {\n if (!person) return '—';\n const full = [person.firstName, person.lastName].filter(Boolean).join(' ').trim();\n return full || person.name || person.email || '—';\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clsx.js","names":[],"sources":["../../../../node_modules/clsx/dist/clsx.mjs"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;"],"x_google_ignoreList":[0],"mappings":";AAAA,SAAS,EAAE,GAAE;CAAC,IAAI,GAAE,GAAE,IAAE;AAAG,KAAa,OAAO,KAAjB,YAA8B,OAAO,KAAjB,SAAmB,MAAG;UAAoB,OAAO,KAAjB,SAAmB,KAAG,MAAM,QAAQ,EAAE,EAAC;EAAC,IAAI,IAAE,EAAE;AAAO,OAAI,IAAE,GAAE,IAAE,GAAE,IAAI,GAAE,OAAK,IAAE,EAAE,EAAE,GAAG,MAAI,MAAI,KAAG,MAAK,KAAG;OAAQ,MAAI,KAAK,EAAE,GAAE,OAAK,MAAI,KAAG,MAAK,KAAG;AAAG,QAAO;;AAAE,SAAgB,IAAM;AAAC,MAAI,IAAI,GAAE,GAAE,IAAE,GAAE,IAAE,IAAG,IAAE,UAAU,QAAO,IAAE,GAAE,IAAI,EAAC,IAAE,UAAU,QAAM,IAAE,EAAE,EAAE,MAAI,MAAI,KAAG,MAAK,KAAG;AAAG,QAAO"}
|