@burdenoff/microfe-buildmyiq 2026.720.4 → 2026.720.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"AssessmentDetailPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"sourcesContent":["/**\n * AssessmentDetailPage — a single assessment + its question builder.\n * @module buildmyiq/pages/assessments\n *\n * Reads `buiAssessment` (which includes `questions[]`). Educators can publish a\n * DRAFT assessment (`useBuiPublishAssessment`) and add questions\n * (`useBuiCreateQuestion`). The question builder writes an `options` JSON array\n * of `{ label }` for choice questions and an optional input-only `answerKey`\n * (correct label(s)) — the latter is never read back (not exposed on the type).\n */\n\nimport { useMemo, useState, type FC, type ReactNode } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Plus,\n Send,\n ClipboardCheck,\n ClipboardEdit,\n Circle,\n Square,\n Clock,\n Trophy,\n Repeat,\n Trash2,\n Lightbulb,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Checkbox,\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 Section,\n EmptyState,\n LoadingView,\n ErrorView,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiAssessment, useBuiPublishAssessment, useBuiCreateQuestion } from '../../hooks';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport { QUESTION_TYPES } from '../../types';\nimport type { BuiQuestion, BuiQuestionType } from '../../types';\n\n// ---------------------------------------------------------------------------\n// Safe narrowing for the untyped `options` JSON blob (array of { id?, label }).\n// ---------------------------------------------------------------------------\ninterface QuestionOption {\n id?: string;\n label: string;\n}\n\nconst parseOptions = (raw: unknown): QuestionOption[] => {\n if (!Array.isArray(raw)) return [];\n const out: QuestionOption[] = [];\n for (const item of raw) {\n if (item && typeof item === 'object' && 'label' in item) {\n const label = (item as { label: unknown }).label;\n if (typeof label === 'string') {\n const idVal = (item as { id?: unknown }).id;\n out.push({ label, id: typeof idVal === 'string' ? idVal : undefined });\n }\n }\n }\n return out;\n};\n\nconst PROMPT_MAX = 1000;\n\nconst CHOICE_TYPES = new Set<BuiQuestionType>(['SINGLE_CHOICE', 'MULTIPLE_CHOICE']);\nconst isExclusive = (type: BuiQuestionType): boolean =>\n type === 'SINGLE_CHOICE' || type === 'TRUE_FALSE';\n\ninterface OptionDraft {\n label: string;\n correct: boolean;\n}\n\ninterface QuestionFormState {\n prompt: string;\n type: BuiQuestionType;\n points: string;\n explanation: string;\n options: OptionDraft[];\n shortAnswer: string;\n}\n\nconst blankChoiceOptions = (): OptionDraft[] => [\n { label: '', correct: false },\n { label: '', correct: false },\n];\n\nconst trueFalseOptions = (): OptionDraft[] => [\n { label: 'True', correct: false },\n { label: 'False', correct: false },\n];\n\nconst makeEmptyForm = (): QuestionFormState => ({\n prompt: '',\n type: 'SINGLE_CHOICE',\n points: '1',\n explanation: '',\n options: blankChoiceOptions(),\n shortAnswer: '',\n});\n\nexport const AssessmentDetailPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const publishAssessment = useBuiPublishAssessment();\n const createQuestion = useBuiCreateQuestion();\n\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<QuestionFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const assessment = assessmentQuery.data ?? null;\n const questions = useMemo(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const nextPosition = useMemo(\n () => (questions.length ? Math.max(...questions.map((q) => q.position)) + 1 : 1),\n [questions]\n );\n\n const totalPoints = useMemo(() => questions.reduce((sum, q) => sum + q.points, 0), [questions]);\n\n const usesOptions = form.type === 'TRUE_FALSE' || CHOICE_TYPES.has(form.type);\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const changeType = (type: BuiQuestionType) => {\n setForm((prev) => {\n let options = prev.options;\n if (type === 'TRUE_FALSE') options = trueFalseOptions();\n else if (CHOICE_TYPES.has(type)) {\n options = prev.options.some((o) => o.label === 'True' || o.label === 'False')\n ? blankChoiceOptions()\n : prev.options;\n }\n return { ...prev, type, options };\n });\n };\n\n const setOptionLabel = (index: number, label: string) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) => (i === index ? { ...o, label } : o)),\n }));\n };\n\n const toggleCorrect = (index: number, value: boolean) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) =>\n i === index\n ? { ...o, correct: value }\n : isExclusive(prev.type)\n ? { ...o, correct: false }\n : o\n ),\n }));\n };\n\n const addOption = () => {\n setForm((prev) => ({ ...prev, options: [...prev.options, { label: '', correct: false }] }));\n };\n\n const removeOption = (index: number) => {\n setForm((prev) => ({ ...prev, options: prev.options.filter((_, i) => i !== index) }));\n };\n\n const handlePublish = async () => {\n if (!assessment) return;\n try {\n await publishAssessment.mutateAsync(assessment.id);\n } catch {\n // The mutation surfaces its own error state; nothing else to do here.\n }\n };\n\n const handleAddQuestion = async () => {\n if (!assessmentId) return;\n if (!form.prompt.trim()) {\n setFormError('Question prompt is required.');\n return;\n }\n\n let options: { label: string }[] | undefined;\n let answerKey: string[] | string | undefined;\n\n if (usesOptions) {\n const cleaned = form.options\n .map((o) => ({ label: o.label.trim(), correct: o.correct }))\n .filter((o) => o.label.length > 0);\n if (CHOICE_TYPES.has(form.type) && cleaned.length < 2) {\n setFormError('Add at least two options.');\n return;\n }\n options = cleaned.map((o) => ({ label: o.label }));\n const correct = cleaned.filter((o) => o.correct).map((o) => o.label);\n if (correct.length) answerKey = correct;\n } else if (form.type === 'SHORT_ANSWER') {\n answerKey = form.shortAnswer.trim() || undefined;\n }\n\n try {\n await createQuestion.mutateAsync({\n assessmentId,\n prompt: form.prompt.trim(),\n type: form.type,\n points: form.points ? Number(form.points) : undefined,\n position: nextPosition,\n explanation: form.explanation.trim() || undefined,\n options,\n answerKey,\n });\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to add question.');\n }\n };\n\n // ----- Loading / error / not-found -----\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError) {\n return (\n <PageLayout title=\"Assessment\">\n <ErrorView\n message=\"Couldn't load this assessment.\"\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!assessment) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Assessment not found\"\n description=\"This assessment may have been removed or you don't have access to it.\"\n action={<Button onClick={() => navigateTo('assessments')}>Back to assessments</Button>}\n />\n </PageLayout>\n );\n }\n\n const isDraft = assessment.status === 'DRAFT';\n\n return (\n <PageLayout\n title={\n <span className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-[var(--color-accent-teal)]\">\n <ClipboardEdit className=\"h-5 w-5\" />\n </span>\n <span className=\"truncate\">{assessment.title}</span>\n </span>\n }\n description={assessment.description ?? undefined}\n actions={\n <>\n <Button variant=\"ghost\" onClick={() => navigateTo('submissions')}>\n <ClipboardCheck className=\"mr-1.5 h-4 w-4\" /> Submissions\n </Button>\n {isDraft ? (\n <Button onClick={handlePublish} disabled={publishAssessment.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishAssessment.isPending ? 'Publishing…' : 'Publish'}\n </Button>\n ) : (\n <StatusBadge status={assessment.status} className=\"px-3 py-1.5\" />\n )}\n </>\n }\n >\n <button\n type=\"button\"\n onClick={() => navigateTo('assessments')}\n className=\"mb-4 inline-flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> All assessments\n </button>\n\n {/* Meta */}\n <GlassCard treatment=\"glass\" className=\"mb-6 p-5 sm:p-6\">\n <div className=\"mb-5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={assessment.type} />\n <StatusBadge status={assessment.status} />\n {assessment.isGraded ? (\n <StatusBadge status=\"GRADED\" />\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n Not graded\n </span>\n )}\n </div>\n <div className=\"grid grid-cols-2 gap-y-6 sm:grid-cols-4\">\n <MetaTile\n icon={<Trophy className=\"h-5 w-5\" />}\n label=\"Passing score\"\n value={`${assessment.passingScore}%`}\n />\n <MetaTile\n icon={<Clock className=\"h-5 w-5\" />}\n label=\"Time limit\"\n value={formatDuration(assessment.timeLimitMin)}\n divider\n />\n <MetaTile\n icon={<Repeat className=\"h-5 w-5\" />}\n label=\"Max attempts\"\n value={assessment.maxAttempts}\n divider\n />\n <MetaTile\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n label=\"Graded\"\n value={assessment.isGraded ? 'Yes' : 'No'}\n divider\n />\n </div>\n </GlassCard>\n\n {/* Questions */}\n <Section\n title={`Questions (${questions.length})`}\n description={\n questions.length > 0\n ? `${totalPoints} point${totalPoints === 1 ? '' : 's'} across ${questions.length} question${\n questions.length === 1 ? '' : 's'\n }`\n : undefined\n }\n action={\n <Button variant=\"outline\" onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n }\n >\n {questions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No questions yet\"\n description=\"Build this assessment by adding its first question.\"\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n }\n />\n ) : (\n <ol className=\"flex flex-col gap-3\">\n {questions.map((q, idx) => (\n <QuestionRow key={q.id} index={idx + 1} question={q} />\n ))}\n </ol>\n )}\n </Section>\n\n {/* Add-question dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add question</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Question {nextPosition} · pick a type, write the prompt and mark the correct\n answer(s).\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prompt\">Prompt *</Label>\n <Textarea\n id=\"prompt\"\n value={form.prompt}\n onChange={(e) => setForm({ ...form, prompt: e.target.value.slice(0, PROMPT_MAX) })}\n placeholder=\"Enter your question prompt…\"\n rows={3}\n maxLength={PROMPT_MAX}\n />\n <p className=\"text-right text-xs tabular-nums text-text-secondary\">\n {form.prompt.length} / {PROMPT_MAX}\n </p>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"questionType\">Type</Label>\n <Select value={form.type} onValueChange={(v) => changeType(v as BuiQuestionType)}>\n <SelectTrigger id=\"questionType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {QUESTION_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"points\">Points</Label>\n <Input\n id=\"points\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={form.points}\n onChange={(e) => setForm({ ...form, points: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n\n {usesOptions && (\n <div className=\"grid gap-2\">\n <Label>Options{form.type !== 'TRUE_FALSE' && ' *'}</Label>\n <p className=\"-mt-1 text-xs text-text-secondary\">\n Tick the {isExclusive(form.type) ? 'single correct answer' : 'correct answers'}.\n </p>\n <div className=\"flex flex-col gap-2\">\n {form.options.map((opt, i) => (\n <div\n key={i}\n className={cn(\n 'flex items-center gap-2.5 rounded-lg border px-2.5 py-1.5 transition-colors',\n opt.correct\n ? 'border-status-success-text bg-status-success-bg-subtle'\n : 'border-border-seam bg-bg-surface'\n )}\n >\n <Checkbox\n checked={opt.correct}\n onCheckedChange={(c) => toggleCorrect(i, c === true)}\n aria-label=\"Mark option correct\"\n />\n <Input\n value={opt.label}\n readOnly={form.type === 'TRUE_FALSE'}\n onChange={(e) => setOptionLabel(i, e.target.value)}\n placeholder={`Option ${i + 1}`}\n className=\"flex-1 border-0 bg-transparent px-1 shadow-none focus-visible:ring-0\"\n />\n {opt.correct && (\n <span className=\"shrink-0 text-xs font-medium text-status-success-text\">\n Correct\n </span>\n )}\n {form.type !== 'TRUE_FALSE' && form.options.length > 2 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeOption(i)}\n aria-label=\"Remove option\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n ))}\n </div>\n {form.type !== 'TRUE_FALSE' && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={addOption}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add option\n </Button>\n )}\n </div>\n )}\n\n {form.type === 'SHORT_ANSWER' && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shortAnswer\">Expected answer</Label>\n <Input\n id=\"shortAnswer\"\n value={form.shortAnswer}\n onChange={(e) => setForm({ ...form, shortAnswer: e.target.value })}\n placeholder=\"The accepted answer (used as the grading key).\"\n />\n </div>\n )}\n\n {(form.type === 'ESSAY' || form.type === 'CODE') && (\n <p className=\"rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {formatEnumLabel(form.type)} responses are graded manually from the Submissions\n view.\n </p>\n )}\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"explanation\">Explanation</Label>\n <Textarea\n id=\"explanation\"\n value={form.explanation}\n onChange={(e) => setForm({ ...form, explanation: e.target.value })}\n placeholder=\"Shown to learners after grading (optional).\"\n rows={2}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button onClick={handleAddQuestion} disabled={createQuestion.isPending}>\n {createQuestion.isPending ? 'Adding…' : 'Add question'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Meta tile — an icon-circle stat used in the assessment summary card.\n// ---------------------------------------------------------------------------\ninterface MetaTileProps {\n icon: ReactNode;\n label: string;\n value: ReactNode;\n /** Draw a subtle left divider on ≥sm (all tiles except the first in the row). */\n divider?: boolean;\n}\n\nconst MetaTile: FC<MetaTileProps> = ({ icon, label, value, divider }) => (\n <div\n className={cn(\n 'flex items-center gap-3',\n divider && 'sm:border-l sm:border-border-seam sm:pl-5'\n )}\n >\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-[var(--color-accent-teal)]\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <p className=\"mt-0.5 text-lg font-semibold tabular-nums text-text-primary\">{value}</p>\n </div>\n </div>\n);\n\n// ---------------------------------------------------------------------------\n// Question row\n// ---------------------------------------------------------------------------\nconst freeTextHint = (type: BuiQuestionType): string => {\n switch (type) {\n case 'SHORT_ANSWER':\n return 'Learners type a short answer — auto-graded against the answer key.';\n case 'ESSAY':\n return 'Open long-form response — graded manually from Submissions.';\n case 'CODE':\n return 'Code response — graded manually from Submissions.';\n default:\n return 'Free-text response.';\n }\n};\n\ninterface QuestionRowProps {\n index: number;\n question: BuiQuestion;\n}\n\nconst QuestionRow: FC<QuestionRowProps> = ({ index, question }) => {\n const options = parseOptions(question.options);\n const isChoice = CHOICE_TYPES.has(question.type) || question.type === 'TRUE_FALSE';\n const isMulti = question.type === 'MULTIPLE_CHOICE';\n\n return (\n <li>\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-5\">\n <div className=\"flex items-start gap-3.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-sm font-bold tabular-nums text-[var(--color-accent-teal)]\">\n {index}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={question.type} />\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {question.points} pt{question.points === 1 ? '' : 's'}\n </span>\n </div>\n\n <p className=\"mt-2.5 font-medium text-text-primary\">{question.prompt}</p>\n\n {isChoice && options.length > 0 && (\n <ul className=\"mt-3 flex flex-col gap-2\">\n {options.map((opt, i) => (\n <li\n key={opt.id ?? `${i}-${opt.label}`}\n className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {isMulti ? (\n <Square className=\"h-4 w-4 shrink-0 text-text-secondary opacity-60\" />\n ) : (\n <Circle className=\"h-4 w-4 shrink-0 text-text-secondary opacity-60\" />\n )}\n <span className=\"min-w-0\">{opt.label}</span>\n </li>\n ))}\n </ul>\n )}\n\n {!isChoice && (\n <p className=\"mt-3 rounded-lg border border-dashed border-border-seam bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {freeTextHint(question.type)}\n </p>\n )}\n\n {question.explanation && (\n <p className=\"mt-3 flex items-start gap-1.5 rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <Lightbulb className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-warning-text\" />\n <span>{question.explanation}</span>\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n </li>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqEA,IAAM,KAAgB,MAAmC;CACvD,IAAI,CAAC,MAAM,QAAQ,CAAG,GAAG,OAAO,CAAC;CACjC,IAAM,IAAwB,CAAC;CAC/B,KAAK,IAAM,KAAQ,GACjB,IAAI,KAAQ,OAAO,KAAS,YAAY,WAAW,GAAM;EACvD,IAAM,IAAS,EAA4B;EAC3C,IAAI,OAAO,KAAU,UAAU;GAC7B,IAAM,IAAS,EAA0B;GACzC,EAAI,KAAK;IAAE;IAAO,IAAI,OAAO,KAAU,WAAW,IAAQ,KAAA;GAAU,CAAC;EACvE;CACF;CAEF,OAAO;AACT,GAEM,IAAa,KAEb,IAAe,IAAI,IAAqB,CAAC,iBAAiB,iBAAiB,CAAC,GAC5E,KAAe,MACnB,MAAS,mBAAmB,MAAS,cAgBjC,UAA0C,CAC9C;CAAE,OAAO;CAAI,SAAS;AAAM,GAC5B;CAAE,OAAO;CAAI,SAAS;AAAM,CAC9B,GAEM,UAAwC,CAC5C;CAAE,OAAO;CAAQ,SAAS;AAAM,GAChC;CAAE,OAAO;CAAS,SAAS;AAAM,CACnC,GAEM,WAA0C;CAC9C,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,aAAa;CACb,SAAS,EAAmB;CAC5B,aAAa;AACf,IAEa,UAAiC;CAC5C,IAAM,EAAE,oBAAiB,GAAoC,GACvD,EAAE,kBAAe,EAAa,GAE9B,IAAkB,GAAiB,CAAY,GAC/C,IAAoB,EAAwB,GAC5C,IAAiB,EAAqB,GAEtC,CAAC,GAAY,KAAiB,EAAS,EAAK,GAC5C,CAAC,GAAM,KAAW,EAA4B,CAAa,GAC3D,CAAC,GAAW,KAAgB,EAAwB,IAAI,GAExD,IAAa,EAAgB,QAAQ,MACrC,IAAY,QACV,CAAC,GAAI,GAAY,aAAa,CAAC,CAAE,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,GAC/E,CAAC,CAAU,CACb,GAEM,IAAe,QACZ,EAAU,SAAS,KAAK,IAAI,GAAG,EAAU,KAAK,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAI,GAC9E,CAAC,CAAS,CACZ,GAEM,IAAc,QAAc,EAAU,QAAQ,GAAK,MAAM,IAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAS,CAAC,GAExF,IAAc,EAAK,SAAS,gBAAgB,EAAa,IAAI,EAAK,IAAI,GAEtE,UAAmB;EAGvB,AAFA,EAAQ,EAAc,CAAC,GACvB,EAAa,IAAI,GACjB,EAAc,EAAI;CACpB,GAEM,UAAoB;EAGxB,AAFA,EAAc,EAAK,GACnB,EAAQ,EAAc,CAAC,GACvB,EAAa,IAAI;CACnB,GAEM,MAAc,MAA0B;EAC5C,GAAS,MAAS;GAChB,IAAI,IAAU,EAAK;GAOnB,OANI,MAAS,eAAc,IAAU,EAAiB,IAC7C,EAAa,IAAI,CAAI,MAC5B,IAAU,EAAK,QAAQ,MAAM,MAAM,EAAE,UAAU,UAAU,EAAE,UAAU,OAAO,IACxE,EAAmB,IACnB,EAAK,UAEJ;IAAE,GAAG;IAAM;IAAM;GAAQ;EAClC,CAAC;CACH,GAEM,MAAkB,GAAe,MAAkB;EACvD,GAAS,OAAU;GACjB,GAAG;GACH,SAAS,EAAK,QAAQ,KAAK,GAAG,MAAO,MAAM,IAAQ;IAAE,GAAG;IAAG;GAAM,IAAI,CAAE;EACzE,EAAE;CACJ,GAEM,MAAiB,GAAe,MAAmB;EACvD,GAAS,OAAU;GACjB,GAAG;GACH,SAAS,EAAK,QAAQ,KAAK,GAAG,MAC5B,MAAM,IACF;IAAE,GAAG;IAAG,SAAS;GAAM,IACvB,EAAY,EAAK,IAAI,IACnB;IAAE,GAAG;IAAG,SAAS;GAAM,IACvB,CACR;EACF,EAAE;CACJ,GAEM,WAAkB;EACtB,GAAS,OAAU;GAAE,GAAG;GAAM,SAAS,CAAC,GAAG,EAAK,SAAS;IAAE,OAAO;IAAI,SAAS;GAAM,CAAC;EAAE,EAAE;CAC5F,GAEM,MAAgB,MAAkB;EACtC,GAAS,OAAU;GAAE,GAAG;GAAM,SAAS,EAAK,QAAQ,QAAQ,GAAG,MAAM,MAAM,CAAK;EAAE,EAAE;CACtF,GAEM,IAAgB,YAAY;EAC3B,OACL,IAAI;GACF,MAAM,EAAkB,YAAY,EAAW,EAAE;EACnD,QAAQ,CAER;CACF,GAEM,KAAoB,YAAY;EACpC,IAAI,CAAC,GAAc;EACnB,IAAI,CAAC,EAAK,OAAO,KAAK,GAAG;GACvB,EAAa,8BAA8B;GAC3C;EACF;EAEA,IAAI,GACA;EAEJ,IAAI,GAAa;GACf,IAAM,IAAU,EAAK,QAClB,KAAK,OAAO;IAAE,OAAO,EAAE,MAAM,KAAK;IAAG,SAAS,EAAE;GAAQ,EAAE,EAC1D,QAAQ,MAAM,EAAE,MAAM,SAAS,CAAC;GACnC,IAAI,EAAa,IAAI,EAAK,IAAI,KAAK,EAAQ,SAAS,GAAG;IACrD,EAAa,2BAA2B;IACxC;GACF;GACA,IAAU,EAAQ,KAAK,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE;GACjD,IAAM,IAAU,EAAQ,QAAQ,MAAM,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK;GACnE,AAAI,EAAQ,WAAQ,IAAY;EAClC,OAAO,AAAI,EAAK,SAAS,mBACvB,IAAY,EAAK,YAAY,KAAK,KAAK,KAAA;EAGzC,IAAI;GAWF,AAVA,MAAM,EAAe,YAAY;IAC/B;IACA,QAAQ,EAAK,OAAO,KAAK;IACzB,MAAM,EAAK;IACX,QAAQ,EAAK,SAAS,OAAO,EAAK,MAAM,IAAI,KAAA;IAC5C,UAAU;IACV,aAAa,EAAK,YAAY,KAAK,KAAK,KAAA;IACxC;IACA;GACF,CAAC,GACD,EAAY;EACd,SAAS,GAAK;GACZ,EAAa,aAAe,QAAQ,EAAI,UAAU,yBAAyB;EAC7E;CACF;CAGA,IAAI,EAAgB,WAClB,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,IAAD,EAAa,OAAM,sBAAuB,CAAA;CAChC,CAAA;CAIhB,IAAI,EAAgB,SAClB,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,IAAD;GACE,SAAQ;GACR,eAAe,EAAgB,QAAQ;EACxC,CAAA;CACS,CAAA;CAIhB,IAAI,CAAC,GACH,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QAAQ,kBAAC,GAAD;IAAQ,eAAe,EAAW,aAAa;cAAG;GAA2B,CAAA;EACtF,CAAA;CACS,CAAA;CAIhB,IAAM,KAAU,EAAW,WAAW;CAEtC,OACE,kBAAC,GAAD;EACE,OACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,QAAD;IAAM,WAAU;cACd,kBAAC,IAAD,EAAe,WAAU,UAAW,CAAA;GAChC,CAAA,GACN,kBAAC,QAAD;IAAM,WAAU;cAAY,EAAW;GAAY,CAAA,CAC/C;;EAER,aAAa,EAAW,eAAe,KAAA;EACvC,SACE,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,eAAe,EAAW,aAAa;aAA/D,CACE,kBAAC,GAAD,EAAgB,WAAU,iBAAkB,CAAA,GAAC,cACvC;MACP,KACC,kBAAC,GAAD;GAAQ,SAAS;GAAe,UAAU,EAAkB;aAA5D,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GACjC,EAAkB,YAAY,gBAAgB,SACzC;OAER,kBAAC,GAAD;GAAa,QAAQ,EAAW;GAAQ,WAAU;EAAe,CAAA,CAEnE,EAAA,CAAA;YAvBN;GA0BE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,aAAa;IACvC,WAAU;cAHZ,CAKE,kBAAC,IAAD,EAAW,WAAU,UAAW,CAAA,GAAC,kBAC3B;;GAGR,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAa,QAAQ,EAAW,KAAO,CAAA;MACvC,kBAAC,GAAD,EAAa,QAAQ,EAAW,OAAS,CAAA;MACxC,EAAW,WACV,kBAAC,GAAD,EAAa,QAAO,SAAU,CAAA,IAE9B,kBAAC,QAAD;OAAM,WAAU;iBAAmH;MAE7H,CAAA;KAEL;QACL,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,MAAM,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;OACnC,OAAM;OACN,OAAO,GAAG,EAAW,aAAa;MACnC,CAAA;MACD,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OAClC,OAAM;OACN,OAAO,EAAe,EAAW,YAAY;OAC7C,SAAA;MACD,CAAA;MACD,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;OACnC,OAAM;OACN,OAAO,EAAW;OAClB,SAAA;MACD,CAAA;MACD,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAgB,WAAU,UAAW,CAAA;OAC3C,OAAM;OACN,OAAO,EAAW,WAAW,QAAQ;OACrC,SAAA;MACD,CAAA;KACE;MACI;;GAGX,kBAAC,IAAD;IACE,OAAO,cAAc,EAAU,OAAO;IACtC,aACE,EAAU,SAAS,IACf,GAAG,EAAY,QAAQ,MAAgB,IAAI,KAAK,IAAI,UAAU,EAAU,OAAO,WAC7E,EAAU,WAAW,IAAI,KAAK,QAEhC,KAAA;IAEN,QACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,SAAS;eAAnC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,eAC7B;;cAGT,EAAU,WAAW,IACpB,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,SAAS;gBAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,eAC7B;;IAEX,CAAA,IAED,kBAAC,MAAD;KAAI,WAAU;eACX,EAAU,KAAK,GAAG,MACjB,kBAAC,IAAD;MAAwB,OAAO,IAAM;MAAG,UAAU;KAAI,GAApC,EAAE,EAAkC,CACvD;IACC,CAAA;GAEC,CAAA;GAGT,kBAAC,IAAD;IACE,MAAM;IACN,eAAe,MAAS;KACtB,AAAK,IACA,EAAc,EAAI,IADZ,EAAY;IAEzB;cAEA,kBAAC,IAAD,EAAA,UAAA;KACE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAuB,eAAmC,CAAA,GAC1D,kBAAC,IAAD,EAAA,UAAA;MAA6B;MACjB;MAAa;KAEI,EAAA,CAAA,CACP,EAAA,CAAA;KAExB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAO,SAAQ;oBAAS;SAAe,CAAA;SACvC,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,GAAG,CAAU;UAAE,CAAC;UACjF,aAAY;UACZ,MAAM;UACN,WAAW;SACZ,CAAA;SACD,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACG,EAAK,OAAO;WAAO;WAAI;UACvB;;QACA;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAe;SAAW,CAAA,GACzC,kBAAC,IAAD;UAAQ,OAAO,EAAK;UAAM,gBAAgB,MAAM,GAAW,CAAoB;oBAA/E,CACE,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAe,KAAK,MACnB,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,CAAC;UACR,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACL;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAS;SAAa,CAAA,GACrC,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,KAAI;UACJ,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,QAAQ,EAAE,OAAO;UAAM,CAAC;UAC5D,aAAY;SACb,CAAA,CACE;UACF;;OAEJ,KACC,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAA,UAAA,CAAO,WAAQ,EAAK,SAAS,gBAAgB,IAAY,EAAA,CAAA;SACzD,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAAiD;WACrC,EAAY,EAAK,IAAI,IAAI,0BAA0B;WAAkB;UAC9E;;SACH,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAK,QAAQ,KAAK,GAAK,MACtB,kBAAC,OAAD;WAEE,WAAW,EACT,+EACA,EAAI,UACA,2DACA,kCACN;qBAPF;YASE,kBAAC,IAAD;aACE,SAAS,EAAI;aACb,kBAAkB,MAAM,GAAc,GAAG,MAAM,EAAI;aACnD,cAAW;YACZ,CAAA;YACD,kBAAC,GAAD;aACE,OAAO,EAAI;aACX,UAAU,EAAK,SAAS;aACxB,WAAW,MAAM,GAAe,GAAG,EAAE,OAAO,KAAK;aACjD,aAAa,UAAU,IAAI;aAC3B,WAAU;YACX,CAAA;YACA,EAAI,WACH,kBAAC,QAAD;aAAM,WAAU;uBAAwD;YAElE,CAAA;YAEP,EAAK,SAAS,gBAAgB,EAAK,QAAQ,SAAS,KACnD,kBAAC,GAAD;aACE,MAAK;aACL,SAAQ;aACR,MAAK;aACL,eAAe,GAAa,CAAC;aAC7B,cAAW;uBAEX,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;YACvB,CAAA;WAEP;aApCE,CAoCF,CACN;SACE,CAAA;SACJ,EAAK,SAAS,gBACb,kBAAC,GAAD;UAAQ,MAAK;UAAS,SAAQ;UAAQ,MAAK;UAAK,SAAS;oBAAzD,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,aAC7B;;QAEP;;OAGN,EAAK,SAAS,kBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAc;QAAsB,CAAA,GACnD,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAK;SACZ,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,aAAa,EAAE,OAAO;SAAM,CAAC;SACjE,aAAY;QACb,CAAA,CACE;;QAGL,EAAK,SAAS,WAAW,EAAK,SAAS,WACvC,kBAAC,KAAD;QAAG,WAAU;kBAAb,CACG,EAAgB,EAAK,IAAI,GAAE,2DAE3B;;OAGL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAc;QAAkB,CAAA,GAC/C,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAK;SACZ,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,aAAa,EAAE,OAAO;SAAM,CAAC;SACjE,aAAY;SACZ,MAAM;QACP,CAAA,CACE;;OAEJ,KAAa,kBAAC,KAAD;QAAG,WAAU;kBAAkC;OAAa,CAAA;MACvE;;KAEL,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,SAAS;gBAAa;KAExC,CAAA,GACR,kBAAC,GAAD;MAAQ,SAAS;MAAmB,UAAU,EAAe;gBAC1D,EAAe,YAAY,YAAY;KAClC,CAAA,CACc,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA;EACR;;AAEhB,GAaM,KAA+B,EAAE,SAAM,UAAO,UAAO,iBACzD,kBAAC,OAAD;CACE,WAAW,EACT,2BACA,KAAW,2CACb;WAJF,CAME,kBAAC,QAAD;EAAM,WAAU;YACb;CACG,CAAA,GACN,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,KAAD;GAAG,WAAU;aAAmE;EAAS,CAAA,GACzF,kBAAC,KAAD;GAAG,WAAU;aAA+D;EAAS,CAAA,CAClF;GACF;IAMD,KAAgB,MAAkC;CACtD,QAAQ,GAAR;EACE,KAAK,gBACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAO;CACX;AACF,GAOM,MAAqC,EAAE,UAAO,kBAAe;CACjE,IAAM,IAAU,EAAa,EAAS,OAAO,GACvC,IAAW,EAAa,IAAI,EAAS,IAAI,KAAK,EAAS,SAAS,cAChE,IAAU,EAAS,SAAS;CAElC,OACE,kBAAC,MAAD,EAAA,UACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YACrC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cACb;GACG,CAAA,GACN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAS,KAAO,CAAA,GACrC,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG,EAAS;QAAO;QAAI,EAAS,WAAW,IAAI,KAAK;OAC9C;QACH;;KAEL,kBAAC,KAAD;MAAG,WAAU;gBAAwC,EAAS;KAAU,CAAA;KAEvE,KAAY,EAAQ,SAAS,KAC5B,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAQ,KAAK,GAAK,MACjB,kBAAC,MAAD;OAEE,WAAU;iBAFZ,CAKI,EADD,IACE,IAEA,GAFD,EAAQ,WAAU,kDAAmD,CAEA,GAEvE,kBAAC,QAAD;QAAM,WAAU;kBAAW,EAAI;OAAY,CAAA,CACzC;SATG,EAAI,MAAM,GAAG,EAAE,GAAG,EAAI,OASzB,CACL;KACC,CAAA;KAGL,CAAC,KACA,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAa,EAAS,IAAI;KAC1B,CAAA;KAGJ,EAAS,eACR,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACE,kBAAC,GAAD,EAAW,WAAU,uDAAwD,CAAA,GAC7E,kBAAC,QAAD,EAAA,UAAO,EAAS,YAAkB,CAAA,CACjC;;IAEF;KACF;;CACI,CAAA,EACT,CAAA;AAER"}
1
+ {"version":3,"file":"AssessmentDetailPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"sourcesContent":["/**\n * AssessmentDetailPage — a single assessment + its question builder.\n * @module buildmyiq/pages/assessments\n *\n * Reads `buiAssessment` (which includes `questions[]`). Educators can publish a\n * DRAFT assessment (`useBuiPublishAssessment`) and add questions\n * (`useBuiCreateQuestion`). The question builder writes an `options` JSON array\n * of `{ label }` for choice questions and an optional input-only `answerKey`\n * (correct label(s)) — the latter is never read back (not exposed on the type).\n *\n * Published/Draft assessments can also be archived (`useBuiArchiveAssessment`,\n * BOFF-4722) — a soft-delete that releases the assessment's reusable\n * `buildmyiq.assessments.count` quota slot so the workspace can create a\n * replacement without hitting the plan limit.\n */\n\nimport { useMemo, useState, type FC, type ReactNode } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Archive,\n ArrowLeft,\n Plus,\n Send,\n ClipboardCheck,\n ClipboardEdit,\n Circle,\n Square,\n Clock,\n Trophy,\n Repeat,\n Trash2,\n Lightbulb,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Checkbox,\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 Section,\n EmptyState,\n LoadingView,\n ErrorView,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiAssessment,\n useBuiPublishAssessment,\n useBuiArchiveAssessment,\n useBuiCreateQuestion,\n} from '../../hooks';\nimport { BuiPermissionButton } from '../../components/BuiPermissionButton';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport { QUESTION_TYPES } from '../../types';\nimport type { BuiQuestion, BuiQuestionType } from '../../types';\n\n// ---------------------------------------------------------------------------\n// Safe narrowing for the untyped `options` JSON blob (array of { id?, label }).\n// ---------------------------------------------------------------------------\ninterface QuestionOption {\n id?: string;\n label: string;\n}\n\nconst parseOptions = (raw: unknown): QuestionOption[] => {\n if (!Array.isArray(raw)) return [];\n const out: QuestionOption[] = [];\n for (const item of raw) {\n if (item && typeof item === 'object' && 'label' in item) {\n const label = (item as { label: unknown }).label;\n if (typeof label === 'string') {\n const idVal = (item as { id?: unknown }).id;\n out.push({ label, id: typeof idVal === 'string' ? idVal : undefined });\n }\n }\n }\n return out;\n};\n\nconst PROMPT_MAX = 1000;\n\nconst CHOICE_TYPES = new Set<BuiQuestionType>(['SINGLE_CHOICE', 'MULTIPLE_CHOICE']);\nconst isExclusive = (type: BuiQuestionType): boolean =>\n type === 'SINGLE_CHOICE' || type === 'TRUE_FALSE';\n\ninterface OptionDraft {\n label: string;\n correct: boolean;\n}\n\ninterface QuestionFormState {\n prompt: string;\n type: BuiQuestionType;\n points: string;\n explanation: string;\n options: OptionDraft[];\n shortAnswer: string;\n}\n\nconst blankChoiceOptions = (): OptionDraft[] => [\n { label: '', correct: false },\n { label: '', correct: false },\n];\n\nconst trueFalseOptions = (): OptionDraft[] => [\n { label: 'True', correct: false },\n { label: 'False', correct: false },\n];\n\nconst makeEmptyForm = (): QuestionFormState => ({\n prompt: '',\n type: 'SINGLE_CHOICE',\n points: '1',\n explanation: '',\n options: blankChoiceOptions(),\n shortAnswer: '',\n});\n\nexport const AssessmentDetailPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const publishAssessment = useBuiPublishAssessment();\n const archiveAssessment = useBuiArchiveAssessment();\n const createQuestion = useBuiCreateQuestion();\n\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<QuestionFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n const [confirmArchive, setConfirmArchive] = useState(false);\n const [archiveError, setArchiveError] = useState<string | null>(null);\n\n const assessment = assessmentQuery.data ?? null;\n const questions = useMemo(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const nextPosition = useMemo(\n () => (questions.length ? Math.max(...questions.map((q) => q.position)) + 1 : 1),\n [questions]\n );\n\n const totalPoints = useMemo(() => questions.reduce((sum, q) => sum + q.points, 0), [questions]);\n\n const usesOptions = form.type === 'TRUE_FALSE' || CHOICE_TYPES.has(form.type);\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const changeType = (type: BuiQuestionType) => {\n setForm((prev) => {\n let options = prev.options;\n if (type === 'TRUE_FALSE') options = trueFalseOptions();\n else if (CHOICE_TYPES.has(type)) {\n options = prev.options.some((o) => o.label === 'True' || o.label === 'False')\n ? blankChoiceOptions()\n : prev.options;\n }\n return { ...prev, type, options };\n });\n };\n\n const setOptionLabel = (index: number, label: string) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) => (i === index ? { ...o, label } : o)),\n }));\n };\n\n const toggleCorrect = (index: number, value: boolean) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) =>\n i === index\n ? { ...o, correct: value }\n : isExclusive(prev.type)\n ? { ...o, correct: false }\n : o\n ),\n }));\n };\n\n const addOption = () => {\n setForm((prev) => ({ ...prev, options: [...prev.options, { label: '', correct: false }] }));\n };\n\n const removeOption = (index: number) => {\n setForm((prev) => ({ ...prev, options: prev.options.filter((_, i) => i !== index) }));\n };\n\n const handlePublish = async () => {\n if (!assessment) return;\n try {\n await publishAssessment.mutateAsync(assessment.id);\n } catch {\n // The mutation surfaces its own error state; nothing else to do here.\n }\n };\n\n const handleArchive = async () => {\n if (!assessment) return;\n setArchiveError(null);\n try {\n await archiveAssessment.mutateAsync({ id: assessment.id, courseId: assessment.courseId });\n setConfirmArchive(false);\n navigateTo('assessments');\n } catch (err) {\n setArchiveError(err instanceof Error ? err.message : 'Failed to archive this assessment.');\n }\n };\n\n const handleAddQuestion = async () => {\n if (!assessmentId) return;\n if (!form.prompt.trim()) {\n setFormError('Question prompt is required.');\n return;\n }\n\n let options: { label: string }[] | undefined;\n let answerKey: string[] | string | undefined;\n\n if (usesOptions) {\n const cleaned = form.options\n .map((o) => ({ label: o.label.trim(), correct: o.correct }))\n .filter((o) => o.label.length > 0);\n if (CHOICE_TYPES.has(form.type) && cleaned.length < 2) {\n setFormError('Add at least two options.');\n return;\n }\n options = cleaned.map((o) => ({ label: o.label }));\n const correct = cleaned.filter((o) => o.correct).map((o) => o.label);\n if (correct.length) answerKey = correct;\n } else if (form.type === 'SHORT_ANSWER') {\n answerKey = form.shortAnswer.trim() || undefined;\n }\n\n try {\n await createQuestion.mutateAsync({\n assessmentId,\n prompt: form.prompt.trim(),\n type: form.type,\n points: form.points ? Number(form.points) : undefined,\n position: nextPosition,\n explanation: form.explanation.trim() || undefined,\n options,\n answerKey,\n });\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to add question.');\n }\n };\n\n // ----- Loading / error / not-found -----\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError) {\n return (\n <PageLayout title=\"Assessment\">\n <ErrorView\n message=\"Couldn't load this assessment.\"\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!assessment) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Assessment not found\"\n description=\"This assessment may have been removed or you don't have access to it.\"\n action={<Button onClick={() => navigateTo('assessments')}>Back to assessments</Button>}\n />\n </PageLayout>\n );\n }\n\n const isDraft = assessment.status === 'DRAFT';\n const isArchived = assessment.status === 'ARCHIVED';\n\n return (\n <PageLayout\n title={\n <span className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-[var(--color-accent-teal)]\">\n <ClipboardEdit className=\"h-5 w-5\" />\n </span>\n <span className=\"truncate\">{assessment.title}</span>\n </span>\n }\n description={assessment.description ?? undefined}\n actions={\n <>\n <Button variant=\"ghost\" onClick={() => navigateTo('submissions')}>\n <ClipboardCheck className=\"mr-1.5 h-4 w-4\" /> Submissions\n </Button>\n {!isArchived && (\n <BuiPermissionButton\n resource=\"buildmyiq-assessment\"\n action=\"delete\"\n deniedBehavior=\"hide\"\n >\n <Button variant=\"outline\" onClick={() => setConfirmArchive(true)}>\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive\n </Button>\n </BuiPermissionButton>\n )}\n {isDraft ? (\n <Button onClick={handlePublish} disabled={publishAssessment.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishAssessment.isPending ? 'Publishing…' : 'Publish'}\n </Button>\n ) : (\n <StatusBadge status={assessment.status} className=\"px-3 py-1.5\" />\n )}\n </>\n }\n >\n <button\n type=\"button\"\n onClick={() => navigateTo('assessments')}\n className=\"mb-4 inline-flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> All assessments\n </button>\n\n {archiveError && (\n <p className=\"mb-4 rounded-lg border border-status-error-bg-subtle bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {archiveError}\n </p>\n )}\n\n {/* Meta */}\n <GlassCard treatment=\"glass\" className=\"mb-6 p-5 sm:p-6\">\n <div className=\"mb-5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={assessment.type} />\n <StatusBadge status={assessment.status} />\n {assessment.isGraded ? (\n <StatusBadge status=\"GRADED\" />\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n Not graded\n </span>\n )}\n </div>\n <div className=\"grid grid-cols-2 gap-y-6 sm:grid-cols-4\">\n <MetaTile\n icon={<Trophy className=\"h-5 w-5\" />}\n label=\"Passing score\"\n value={`${assessment.passingScore}%`}\n />\n <MetaTile\n icon={<Clock className=\"h-5 w-5\" />}\n label=\"Time limit\"\n value={formatDuration(assessment.timeLimitMin)}\n divider\n />\n <MetaTile\n icon={<Repeat className=\"h-5 w-5\" />}\n label=\"Max attempts\"\n value={assessment.maxAttempts}\n divider\n />\n <MetaTile\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n label=\"Graded\"\n value={assessment.isGraded ? 'Yes' : 'No'}\n divider\n />\n </div>\n </GlassCard>\n\n {/* Questions */}\n <Section\n title={`Questions (${questions.length})`}\n description={\n questions.length > 0\n ? `${totalPoints} point${totalPoints === 1 ? '' : 's'} across ${questions.length} question${\n questions.length === 1 ? '' : 's'\n }`\n : undefined\n }\n action={\n <Button variant=\"outline\" onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n }\n >\n {questions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No questions yet\"\n description=\"Build this assessment by adding its first question.\"\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n }\n />\n ) : (\n <ol className=\"flex flex-col gap-3\">\n {questions.map((q, idx) => (\n <QuestionRow key={q.id} index={idx + 1} question={q} />\n ))}\n </ol>\n )}\n </Section>\n\n {/* Add-question dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add question</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Question {nextPosition} · pick a type, write the prompt and mark the correct\n answer(s).\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prompt\">Prompt *</Label>\n <Textarea\n id=\"prompt\"\n value={form.prompt}\n onChange={(e) => setForm({ ...form, prompt: e.target.value.slice(0, PROMPT_MAX) })}\n placeholder=\"Enter your question prompt…\"\n rows={3}\n maxLength={PROMPT_MAX}\n />\n <p className=\"text-right text-xs tabular-nums text-text-secondary\">\n {form.prompt.length} / {PROMPT_MAX}\n </p>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"questionType\">Type</Label>\n <Select value={form.type} onValueChange={(v) => changeType(v as BuiQuestionType)}>\n <SelectTrigger id=\"questionType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {QUESTION_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"points\">Points</Label>\n <Input\n id=\"points\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={form.points}\n onChange={(e) => setForm({ ...form, points: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n\n {usesOptions && (\n <div className=\"grid gap-2\">\n <Label>Options{form.type !== 'TRUE_FALSE' && ' *'}</Label>\n <p className=\"-mt-1 text-xs text-text-secondary\">\n Tick the {isExclusive(form.type) ? 'single correct answer' : 'correct answers'}.\n </p>\n <div className=\"flex flex-col gap-2\">\n {form.options.map((opt, i) => (\n <div\n key={i}\n className={cn(\n 'flex items-center gap-2.5 rounded-lg border px-2.5 py-1.5 transition-colors',\n opt.correct\n ? 'border-status-success-text bg-status-success-bg-subtle'\n : 'border-border-seam bg-bg-surface'\n )}\n >\n <Checkbox\n checked={opt.correct}\n onCheckedChange={(c) => toggleCorrect(i, c === true)}\n aria-label=\"Mark option correct\"\n />\n <Input\n value={opt.label}\n readOnly={form.type === 'TRUE_FALSE'}\n onChange={(e) => setOptionLabel(i, e.target.value)}\n placeholder={`Option ${i + 1}`}\n className=\"flex-1 border-0 bg-transparent px-1 shadow-none focus-visible:ring-0\"\n />\n {opt.correct && (\n <span className=\"shrink-0 text-xs font-medium text-status-success-text\">\n Correct\n </span>\n )}\n {form.type !== 'TRUE_FALSE' && form.options.length > 2 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeOption(i)}\n aria-label=\"Remove option\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n ))}\n </div>\n {form.type !== 'TRUE_FALSE' && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={addOption}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add option\n </Button>\n )}\n </div>\n )}\n\n {form.type === 'SHORT_ANSWER' && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shortAnswer\">Expected answer</Label>\n <Input\n id=\"shortAnswer\"\n value={form.shortAnswer}\n onChange={(e) => setForm({ ...form, shortAnswer: e.target.value })}\n placeholder=\"The accepted answer (used as the grading key).\"\n />\n </div>\n )}\n\n {(form.type === 'ESSAY' || form.type === 'CODE') && (\n <p className=\"rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {formatEnumLabel(form.type)} responses are graded manually from the Submissions\n view.\n </p>\n )}\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"explanation\">Explanation</Label>\n <Textarea\n id=\"explanation\"\n value={form.explanation}\n onChange={(e) => setForm({ ...form, explanation: e.target.value })}\n placeholder=\"Shown to learners after grading (optional).\"\n rows={2}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button onClick={handleAddQuestion} disabled={createQuestion.isPending}>\n {createQuestion.isPending ? 'Adding…' : 'Add question'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* Archive confirmation */}\n <ResponsiveDialog\n open={confirmArchive}\n onOpenChange={(open) => {\n if (!open) setConfirmArchive(false);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Archive this assessment?</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Archived assessments are hidden from the catalog and can no longer be submitted to.\n This frees up its quota slot so you can create a replacement assessment for this\n course.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setConfirmArchive(false)}\n disabled={archiveAssessment.isPending}\n >\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n onClick={handleArchive}\n disabled={archiveAssessment.isPending}\n >\n {archiveAssessment.isPending ? 'Archiving…' : 'Archive assessment'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Meta tile — an icon-circle stat used in the assessment summary card.\n// ---------------------------------------------------------------------------\ninterface MetaTileProps {\n icon: ReactNode;\n label: string;\n value: ReactNode;\n /** Draw a subtle left divider on ≥sm (all tiles except the first in the row). */\n divider?: boolean;\n}\n\nconst MetaTile: FC<MetaTileProps> = ({ icon, label, value, divider }) => (\n <div\n className={cn(\n 'flex items-center gap-3',\n divider && 'sm:border-l sm:border-border-seam sm:pl-5'\n )}\n >\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-[var(--color-accent-teal)]\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">{label}</p>\n <p className=\"mt-0.5 text-lg font-semibold tabular-nums text-text-primary\">{value}</p>\n </div>\n </div>\n);\n\n// ---------------------------------------------------------------------------\n// Question row\n// ---------------------------------------------------------------------------\nconst freeTextHint = (type: BuiQuestionType): string => {\n switch (type) {\n case 'SHORT_ANSWER':\n return 'Learners type a short answer — auto-graded against the answer key.';\n case 'ESSAY':\n return 'Open long-form response — graded manually from Submissions.';\n case 'CODE':\n return 'Code response — graded manually from Submissions.';\n default:\n return 'Free-text response.';\n }\n};\n\ninterface QuestionRowProps {\n index: number;\n question: BuiQuestion;\n}\n\nconst QuestionRow: FC<QuestionRowProps> = ({ index, question }) => {\n const options = parseOptions(question.options);\n const isChoice = CHOICE_TYPES.has(question.type) || question.type === 'TRUE_FALSE';\n const isMulti = question.type === 'MULTIPLE_CHOICE';\n\n return (\n <li>\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-5\">\n <div className=\"flex items-start gap-3.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-sm font-bold tabular-nums text-[var(--color-accent-teal)]\">\n {index}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={question.type} />\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {question.points} pt{question.points === 1 ? '' : 's'}\n </span>\n </div>\n\n <p className=\"mt-2.5 font-medium text-text-primary\">{question.prompt}</p>\n\n {isChoice && options.length > 0 && (\n <ul className=\"mt-3 flex flex-col gap-2\">\n {options.map((opt, i) => (\n <li\n key={opt.id ?? `${i}-${opt.label}`}\n className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {isMulti ? (\n <Square className=\"h-4 w-4 shrink-0 text-text-secondary opacity-60\" />\n ) : (\n <Circle className=\"h-4 w-4 shrink-0 text-text-secondary opacity-60\" />\n )}\n <span className=\"min-w-0\">{opt.label}</span>\n </li>\n ))}\n </ul>\n )}\n\n {!isChoice && (\n <p className=\"mt-3 rounded-lg border border-dashed border-border-seam bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {freeTextHint(question.type)}\n </p>\n )}\n\n {question.explanation && (\n <p className=\"mt-3 flex items-start gap-1.5 rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <Lightbulb className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-warning-text\" />\n <span>{question.explanation}</span>\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n </li>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-buildmyiq",
3
- "version": "2026.720.4",
3
+ "version": "2026.720.5",
4
4
  "description": "Burdenoff BuildMyIQ Microfrontend — education, learning & development",
5
5
  "type": "module",
6
6
  "files": [