@burdenoff/microfe-buildmyiq 2026.705.9 → 2026.705.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buildmyiq/BuildmyiqRoot.js +54 -46
- package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
- package/dist/buildmyiq/BuildmyiqRoutes.js +19 -11
- package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
- package/dist/buildmyiq/components/DataTable.js +1 -1
- package/dist/buildmyiq/components/DataTable.js.map +1 -1
- package/dist/buildmyiq/components/MiniBar.js +1 -1
- package/dist/buildmyiq/components/MiniBar.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js +23 -15
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
- package/dist/buildmyiq/pages/DashboardPage.js +2 -2
- package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +264 -0
- package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -0
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js +4 -4
- package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
- package/dist/buildmyiq/pages/authoring/_forms.js +15 -15
- package/dist/buildmyiq/pages/authoring/_forms.js.map +1 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +9 -1
- package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js +1 -1
- package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js +1 -1
- package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
- package/dist/buildmyiq/pages/insights/InsightsPage.js +242 -0
- package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -0
- package/dist/buildmyiq/pages/learning/MyLearningPage.js +83 -77
- package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuildmyiqApi.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuildmyiqApi.ts"],"sourcesContent":["/**\n * Buildmyiq API hooks\n * @module buildmyiq/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-buildmyiq-svc`\n * federated GraphQL contract (the `bui*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data, no codegen.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\nimport type {\n BuiConnection,\n BuiCourse,\n BuiCourseLevel,\n BuiCourseStatus,\n BuiModule,\n BuiLesson,\n BuiAssessment,\n BuiAssessmentStatus,\n BuiQuestion,\n BuiSubmission,\n BuiSubmissionStatus,\n BuiEnrollment,\n BuiEnrollmentStatus,\n BuiCohort,\n BuiCohortStatus,\n BuiSkill,\n BuiSkillCategory,\n BuiCertificate,\n BuiLessonProgress,\n BuiCreateCourseInput,\n BuiUpdateCourseInput,\n BuiCreateModuleInput,\n BuiCreateLessonInput,\n BuiCreateAssessmentInput,\n BuiCreateQuestionInput,\n BuiCreateSkillInput,\n BuiCreateCohortInput,\n BuiEnrollInput,\n BuiRecordLessonProgressInput,\n BuiSubmitAssessmentInput,\n BuiGradeSubmissionInput,\n BuiIssueCertificateInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. the React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * We clear those caches after every buildmyiq mutation so every follow-up read\n * is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst buiQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n courses: ['buildmyiq', 'courses'] as const,\n course: (id: string) => ['buildmyiq', 'course', id] as const,\n modules: (courseId: string) => ['buildmyiq', 'modules', courseId] as const,\n lessons: (courseId: string) => ['buildmyiq', 'lessons', courseId] as const,\n assessments: (courseId: string) => ['buildmyiq', 'assessments', courseId] as const,\n assessment: (id: string) => ['buildmyiq', 'assessment', id] as const,\n enrollments: ['buildmyiq', 'enrollments'] as const,\n enrollment: (id: string) => ['buildmyiq', 'enrollment', id] as const,\n certificates: ['buildmyiq', 'certificates'] as const,\n cohorts: ['buildmyiq', 'cohorts'] as const,\n skills: ['buildmyiq', 'skills'] as const,\n submissions: ['buildmyiq', 'submissions'] as const,\n};\n\nconst DEFAULT_PAGE_SIZE = 24;\n\n/**\n * Build a `PaginationInput` for a list query. The FIRST page (undefined cursor)\n * OMITS the cursor key entirely — the svc rejects an explicit `cursor: null`.\n */\nconst pageVars = (cursor?: string, limit = DEFAULT_PAGE_SIZE): PaginationInput =>\n cursor ? { limit, cursor } : { limit };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst FILE_FIELDS = `id name downloadUrl mimeType`;\n\nconst SKILL_FIELDS = `\n id workspaceId name category description isActive createdAt updatedAt\n`;\n\nconst COURSE_CORE_FIELDS = `\n id workspaceId title slug summary description status level language\n estimatedHours isPublic tags skillId instructorId publishedAt createdAt updatedAt\n cover { ${FILE_FIELDS} }\n skill { id name category }\n`;\n\nconst MODULE_FIELDS = `\n id workspaceId courseId title description position createdAt updatedAt\n`;\n\nconst LESSON_FIELDS = `\n id workspaceId courseId moduleId title type content durationMin position isPreview\n createdAt updatedAt\n media { ${FILE_FIELDS} }\n`;\n\nconst COURSE_FULL_FIELDS = `\n ${COURSE_CORE_FIELDS}\n modules { ${MODULE_FIELDS} lessons { ${LESSON_FIELDS} } }\n lessons { ${LESSON_FIELDS} }\n`;\n\nconst ASSESSMENT_FIELDS = `\n id workspaceId courseId title description type status passingScore maxAttempts\n timeLimitMin isGraded createdAt updatedAt\n`;\n\n// NOTE: BuiQuestion has NO `answerKey` on the type (create-input only).\nconst QUESTION_FIELDS = `\n id workspaceId assessmentId prompt type options points position explanation\n createdAt updatedAt\n`;\n\nconst ASSESSMENT_FULL_FIELDS = `\n ${ASSESSMENT_FIELDS}\n questions { ${QUESTION_FIELDS} }\n`;\n\nconst SUBMISSION_FIELDS = `\n id workspaceId assessmentId enrollmentId learnerId attempt answers status score\n passed feedback gradedById gradedAt submittedAt createdAt updatedAt\n`;\n\nconst COHORT_FIELDS = `\n id workspaceId courseId name capacity facilitatorId startDate endDate status\n createdAt updatedAt\n`;\n\nconst LESSON_PROGRESS_FIELDS = `\n id workspaceId enrollmentId lessonId status secondsSpent completedAt createdAt updatedAt\n`;\n\nconst ENROLLMENT_FIELDS = `\n id workspaceId courseId learnerId cohortId status progressPct enrolledAt startedAt\n completedAt lastActiveAt expiresAt createdAt updatedAt\n`;\n\nconst ENROLLMENT_FULL_FIELDS = `\n ${ENROLLMENT_FIELDS}\n cohort { ${COHORT_FIELDS} }\n lessonProgress { ${LESSON_PROGRESS_FIELDS} }\n`;\n\nconst CERTIFICATE_FIELDS = `\n id workspaceId courseId learnerId enrollmentId serial status issuedAt expiresAt\n revokedAt finalScore createdAt updatedAt\n file { ${FILE_FIELDS} }\n`;\n\n// ============================================================================\n// Shared hook helper: resolve gateway URL from context\n// ============================================================================\n\nconst useGateway = (): string | undefined => {\n const { apiGatewayUrl } = useBuildmyiq();\n return apiGatewayUrl;\n};\n\n// ============================================================================\n// Courses\n// ============================================================================\n\nexport interface UseBuiCoursesArgs {\n level?: BuiCourseLevel;\n status?: BuiCourseStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiCourses = (args: UseBuiCoursesArgs = {}) => {\n const baseUrl = useGateway();\n const { level, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.courses, { level, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListCourses: BuiConnection<BuiCourse> }>(\n `query BuiListCourses($level: BuiCourseLevel, $status: BuiCourseStatus, $pagination: PaginationInput) {\n buiListCourses(level: $level, status: $status, pagination: $pagination) {\n items { ${COURSE_CORE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { level, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListCourses;\n },\n });\n};\n\nexport const useBuiCourse = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.course(id) : ['buildmyiq', 'course', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiCourse: BuiCourse | null }>(\n `query BuiCourse($id: ID!) {\n buiCourse(id: $id) { ${COURSE_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiCourse;\n },\n });\n};\n\nexport const useBuiCreateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCourseInput) => {\n const data = await buiQuery<{ buiCreateCourse: BuiCourse }>(\n `mutation BuiCreateCourse($input: BuiCreateCourseInput!) {\n buiCreateCourse(input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCourse;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.courses }),\n });\n};\n\nexport const useBuiUpdateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiUpdateCourseInput }) => {\n const data = await buiQuery<{ buiUpdateCourse: BuiCourse }>(\n `mutation BuiUpdateCourse($id: ID!, $input: BuiUpdateCourseInput!) {\n buiUpdateCourse(id: $id, input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiUpdateCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiPublishCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishCourse: BuiCourse }>(\n `mutation BuiPublishCourse($id: ID!) {\n buiPublishCourse(id: $id) { ${COURSE_CORE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiArchiveCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiArchiveCourse: boolean }>(\n `mutation BuiArchiveCourse($id: ID!) { buiArchiveCourse(id: $id) }`,\n { id },\n baseUrl\n );\n return data.buiArchiveCourse;\n },\n onSuccess: (_r, id) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(id) });\n },\n });\n};\n\n// ============================================================================\n// Modules & lessons\n// ============================================================================\n\nexport const useBuiModules = (courseId: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? KEY.modules(courseId) : ['buildmyiq', 'modules', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListModules: BuiModule[] }>(\n `query BuiListModules($courseId: ID!) {\n buiListModules(courseId: $courseId) { ${MODULE_FIELDS} lessons { ${LESSON_FIELDS} } }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiListModules;\n },\n });\n};\n\nexport const useBuiLessons = (courseId: string | undefined, moduleId?: string) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? [...KEY.lessons(courseId), { moduleId }] : ['buildmyiq', 'lessons', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListLessons: BuiLesson[] }>(\n `query BuiListLessons($courseId: ID!, $moduleId: ID) {\n buiListLessons(courseId: $courseId, moduleId: $moduleId) { ${LESSON_FIELDS} }\n }`,\n { courseId, moduleId },\n baseUrl\n );\n return data.buiListLessons;\n },\n });\n};\n\nexport const useBuiCreateModule = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateModuleInput) => {\n const data = await buiQuery<{ buiCreateModule: BuiModule }>(\n `mutation BuiCreateModule($input: BuiCreateModuleInput!) {\n buiCreateModule(input: $input) { ${MODULE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateModule;\n },\n onSuccess: (m) => {\n qc.invalidateQueries({ queryKey: KEY.modules(m.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(m.courseId) });\n },\n });\n};\n\nexport const useBuiCreateLesson = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateLessonInput) => {\n const data = await buiQuery<{ buiCreateLesson: BuiLesson }>(\n `mutation BuiCreateLesson($input: BuiCreateLessonInput!) {\n buiCreateLesson(input: $input) { ${LESSON_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateLesson;\n },\n onSuccess: (l) => {\n qc.invalidateQueries({ queryKey: KEY.lessons(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.modules(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(l.courseId) });\n },\n });\n};\n\n// ============================================================================\n// Assessments & questions\n// ============================================================================\n\nexport const useBuiAssessments = (courseId: string | undefined, status?: BuiAssessmentStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId\n ? [...KEY.assessments(courseId), { status }]\n : ['buildmyiq', 'assessments', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListAssessments: BuiAssessment[] }>(\n `query BuiListAssessments($courseId: ID!, $status: BuiAssessmentStatus) {\n buiListAssessments(courseId: $courseId, status: $status) { ${ASSESSMENT_FIELDS} }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListAssessments;\n },\n });\n};\n\nexport const useBuiAssessment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.assessment(id) : ['buildmyiq', 'assessment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiAssessment: BuiAssessment | null }>(\n `query BuiAssessment($id: ID!) {\n buiAssessment(id: $id) { ${ASSESSMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiAssessment;\n },\n });\n};\n\nexport const useBuiCreateAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateAssessmentInput) => {\n const data = await buiQuery<{ buiCreateAssessment: BuiAssessment }>(\n `mutation BuiCreateAssessment($input: BuiCreateAssessmentInput!) {\n buiCreateAssessment(input: $input) { ${ASSESSMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateAssessment;\n },\n onSuccess: (a) => qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) }),\n });\n};\n\nexport const useBuiPublishAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishAssessment: BuiAssessment }>(\n `mutation BuiPublishAssessment($id: ID!) {\n buiPublishAssessment(id: $id) { ${ASSESSMENT_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishAssessment;\n },\n onSuccess: (a) => {\n qc.invalidateQueries({ queryKey: KEY.assessment(a.id) });\n qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) });\n },\n });\n};\n\nexport const useBuiCreateQuestion = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateQuestionInput) => {\n const data = await buiQuery<{ buiCreateQuestion: BuiQuestion }>(\n `mutation BuiCreateQuestion($input: BuiCreateQuestionInput!) {\n buiCreateQuestion(input: $input) { ${QUESTION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateQuestion;\n },\n onSuccess: (q) => qc.invalidateQueries({ queryKey: KEY.assessment(q.assessmentId) }),\n });\n};\n\n// ============================================================================\n// Submissions & grading\n// ============================================================================\n\nexport interface UseBuiSubmissionsArgs {\n assessmentId?: string;\n learnerId?: string;\n status?: BuiSubmissionStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiSubmissions = (args: UseBuiSubmissionsArgs = {}) => {\n const baseUrl = useGateway();\n const { assessmentId, learnerId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.submissions, { assessmentId, learnerId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListSubmissions: BuiConnection<BuiSubmission> }>(\n `query BuiListSubmissions($assessmentId: ID, $learnerId: ID, $status: BuiSubmissionStatus, $pagination: PaginationInput) {\n buiListSubmissions(assessmentId: $assessmentId, learnerId: $learnerId, status: $status, pagination: $pagination) {\n items { ${SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, learnerId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListSubmissions;\n },\n });\n};\n\nexport const useBuiSubmitAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiSubmitAssessmentInput) => {\n const data = await buiQuery<{ buiSubmitAssessment: BuiSubmission }>(\n `mutation BuiSubmitAssessment($input: BuiSubmitAssessmentInput!) {\n buiSubmitAssessment(input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiSubmitAssessment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\nexport const useBuiGradeSubmission = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiGradeSubmissionInput }) => {\n const data = await buiQuery<{ buiGradeSubmission: BuiSubmission }>(\n `mutation BuiGradeSubmission($id: ID!, $input: BuiGradeSubmissionInput!) {\n buiGradeSubmission(id: $id, input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiGradeSubmission;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\n// ============================================================================\n// Enrollments & progress\n// ============================================================================\n\nexport interface UseBuiEnrollmentsArgs {\n courseId?: string;\n learnerId?: string;\n status?: BuiEnrollmentStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiEnrollments = (args: UseBuiEnrollmentsArgs = {}) => {\n const baseUrl = useGateway();\n const { courseId, learnerId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.enrollments, { courseId, learnerId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListEnrollments: BuiConnection<BuiEnrollment> }>(\n `query BuiListEnrollments($courseId: ID, $learnerId: ID, $status: BuiEnrollmentStatus, $pagination: PaginationInput) {\n buiListEnrollments(courseId: $courseId, learnerId: $learnerId, status: $status, pagination: $pagination) {\n items { ${ENROLLMENT_FIELDS} cohort { ${COHORT_FIELDS} } }\n pageInfo { cursor hasMore }\n }\n }`,\n { courseId, learnerId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListEnrollments;\n },\n });\n};\n\nexport const useBuiEnrollment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.enrollment(id) : ['buildmyiq', 'enrollment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiEnrollment: BuiEnrollment | null }>(\n `query BuiEnrollment($id: ID!) {\n buiEnrollment(id: $id) { ${ENROLLMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiEnrollment;\n },\n });\n};\n\nexport const useBuiEnroll = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiEnrollInput) => {\n const data = await buiQuery<{ buiEnroll: BuiEnrollment }>(\n `mutation BuiEnroll($input: BuiEnrollInput!) {\n buiEnroll(input: $input) { ${ENROLLMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiEnroll;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.enrollments }),\n });\n};\n\nexport const useBuiTransitionEnrollment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; status: BuiEnrollmentStatus }) => {\n const data = await buiQuery<{ buiTransitionEnrollment: BuiEnrollment }>(\n `mutation BuiTransitionEnrollment($id: ID!, $status: BuiEnrollmentStatus!) {\n buiTransitionEnrollment(id: $id, status: $status) { ${ENROLLMENT_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiTransitionEnrollment;\n },\n onSuccess: (e) => {\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n qc.invalidateQueries({ queryKey: KEY.enrollment(e.id) });\n },\n });\n};\n\nexport const useBuiRecordLessonProgress = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiRecordLessonProgressInput) => {\n const data = await buiQuery<{ buiRecordLessonProgress: BuiLessonProgress }>(\n `mutation BuiRecordLessonProgress($input: BuiRecordLessonProgressInput!) {\n buiRecordLessonProgress(input: $input) { ${LESSON_PROGRESS_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiRecordLessonProgress;\n },\n onSuccess: (p) => {\n qc.invalidateQueries({ queryKey: KEY.enrollment(p.enrollmentId) });\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n },\n });\n};\n\n// ============================================================================\n// Cohorts\n// ============================================================================\n\nexport const useBuiCohorts = (courseId?: string, status?: BuiCohortStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.cohorts, { courseId, status }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListCohorts: BuiCohort[] }>(\n `query BuiListCohorts($courseId: ID, $status: BuiCohortStatus) {\n buiListCohorts(courseId: $courseId, status: $status) { ${COHORT_FIELDS} }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListCohorts;\n },\n });\n};\n\nexport const useBuiCreateCohort = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCohortInput) => {\n const data = await buiQuery<{ buiCreateCohort: BuiCohort }>(\n `mutation BuiCreateCohort($input: BuiCreateCohortInput!) {\n buiCreateCohort(input: $input) { ${COHORT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCohort;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.cohorts }),\n });\n};\n\n// ============================================================================\n// Skills\n// ============================================================================\n\nexport const useBuiSkills = (category?: BuiSkillCategory) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.skills, { category }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListSkills: BuiSkill[] }>(\n `query BuiListSkills($category: BuiSkillCategory) {\n buiListSkills(category: $category) { ${SKILL_FIELDS} }\n }`,\n { category },\n baseUrl\n );\n return data.buiListSkills;\n },\n });\n};\n\nexport const useBuiCreateSkill = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateSkillInput) => {\n const data = await buiQuery<{ buiCreateSkill: BuiSkill }>(\n `mutation BuiCreateSkill($input: BuiCreateSkillInput!) {\n buiCreateSkill(input: $input) { ${SKILL_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateSkill;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.skills }),\n });\n};\n\n// ============================================================================\n// Certificates\n// ============================================================================\n\nexport const useBuiCertificates = (courseId?: string, learnerId?: string) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.certificates, { courseId, learnerId }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListCertificates: BuiCertificate[] }>(\n `query BuiListCertificates($courseId: ID, $learnerId: ID) {\n buiListCertificates(courseId: $courseId, learnerId: $learnerId) { ${CERTIFICATE_FIELDS} }\n }`,\n { courseId, learnerId },\n baseUrl\n );\n return data.buiListCertificates;\n },\n });\n};\n\nexport const useBuiIssueCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiIssueCertificateInput) => {\n const data = await buiQuery<{ buiIssueCertificate: BuiCertificate }>(\n `mutation BuiIssueCertificate($input: BuiIssueCertificateInput!) {\n buiIssueCertificate(input: $input) { ${CERTIFICATE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiIssueCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n\nexport const useBuiRevokeCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiRevokeCertificate: BuiCertificate }>(\n `mutation BuiRevokeCertificate($id: ID!) {\n buiRevokeCertificate(id: $id) { ${CERTIFICATE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiRevokeCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useBuildmyiqApi.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuildmyiqApi.ts"],"sourcesContent":["/**\n * Buildmyiq API hooks\n * @module buildmyiq/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-buildmyiq-svc`\n * federated GraphQL contract (the `bui*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data, no codegen.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\nimport type {\n BuiConnection,\n BuiCourse,\n BuiCourseLevel,\n BuiCourseStatus,\n BuiModule,\n BuiLesson,\n BuiAssessment,\n BuiAssessmentStatus,\n BuiQuestion,\n BuiSubmission,\n BuiSubmissionStatus,\n BuiEnrollment,\n BuiEnrollmentStatus,\n BuiCohort,\n BuiCohortStatus,\n BuiSkill,\n BuiSkillCategory,\n BuiCertificate,\n BuiLessonProgress,\n BuiCreateCourseInput,\n BuiUpdateCourseInput,\n BuiCreateModuleInput,\n BuiCreateLessonInput,\n BuiCreateAssessmentInput,\n BuiCreateQuestionInput,\n BuiCreateSkillInput,\n BuiCreateCohortInput,\n BuiEnrollInput,\n BuiRecordLessonProgressInput,\n BuiSubmitAssessmentInput,\n BuiGradeSubmissionInput,\n BuiIssueCertificateInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. the React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * We clear those caches after every buildmyiq mutation so every follow-up read\n * is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst buiQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n courses: ['buildmyiq', 'courses'] as const,\n course: (id: string) => ['buildmyiq', 'course', id] as const,\n modules: (courseId: string) => ['buildmyiq', 'modules', courseId] as const,\n lessons: (courseId: string) => ['buildmyiq', 'lessons', courseId] as const,\n assessments: (courseId: string) => ['buildmyiq', 'assessments', courseId] as const,\n assessment: (id: string) => ['buildmyiq', 'assessment', id] as const,\n enrollments: ['buildmyiq', 'enrollments'] as const,\n enrollment: (id: string) => ['buildmyiq', 'enrollment', id] as const,\n certificates: ['buildmyiq', 'certificates'] as const,\n cohorts: ['buildmyiq', 'cohorts'] as const,\n skills: ['buildmyiq', 'skills'] as const,\n submissions: ['buildmyiq', 'submissions'] as const,\n};\n\nconst DEFAULT_PAGE_SIZE = 24;\n\n/**\n * Build a `PaginationInput` for a list query. The FIRST page (undefined cursor)\n * OMITS the cursor key entirely — the svc rejects an explicit `cursor: null`.\n */\nconst pageVars = (cursor?: string, limit = DEFAULT_PAGE_SIZE): PaginationInput =>\n cursor ? { limit, cursor } : { limit };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst FILE_FIELDS = `id name downloadUrl mimeType`;\n\nconst SKILL_FIELDS = `\n id workspaceId name category description isActive createdAt updatedAt\n`;\n\nconst COURSE_CORE_FIELDS = `\n id workspaceId title slug summary description status level language\n estimatedHours isPublic tags skillId instructorId publishedAt createdAt updatedAt\n cover { ${FILE_FIELDS} }\n skill { id name category }\n`;\n\nconst MODULE_FIELDS = `\n id workspaceId courseId title description position createdAt updatedAt\n`;\n\nconst LESSON_FIELDS = `\n id workspaceId courseId moduleId title type content durationMin position isPreview\n createdAt updatedAt\n media { ${FILE_FIELDS} }\n`;\n\nconst COURSE_FULL_FIELDS = `\n ${COURSE_CORE_FIELDS}\n modules { ${MODULE_FIELDS} lessons { ${LESSON_FIELDS} } }\n lessons { ${LESSON_FIELDS} }\n`;\n\nconst ASSESSMENT_FIELDS = `\n id workspaceId courseId title description type status passingScore maxAttempts\n timeLimitMin isGraded createdAt updatedAt\n`;\n\n// NOTE: BuiQuestion has NO `answerKey` on the type (create-input only).\nconst QUESTION_FIELDS = `\n id workspaceId assessmentId prompt type options points position explanation\n createdAt updatedAt\n`;\n\nconst ASSESSMENT_FULL_FIELDS = `\n ${ASSESSMENT_FIELDS}\n questions { ${QUESTION_FIELDS} }\n`;\n\nconst SUBMISSION_FIELDS = `\n id workspaceId assessmentId enrollmentId learnerId attempt answers status score\n passed feedback gradedById gradedAt submittedAt createdAt updatedAt\n`;\n\nconst COHORT_FIELDS = `\n id workspaceId courseId name capacity facilitatorId startDate endDate status\n createdAt updatedAt\n`;\n\nconst LESSON_PROGRESS_FIELDS = `\n id workspaceId enrollmentId lessonId status secondsSpent completedAt createdAt updatedAt\n`;\n\nconst ENROLLMENT_FIELDS = `\n id workspaceId courseId learnerId cohortId status progressPct enrolledAt startedAt\n completedAt lastActiveAt expiresAt createdAt updatedAt\n`;\n\nconst ENROLLMENT_FULL_FIELDS = `\n ${ENROLLMENT_FIELDS}\n cohort { ${COHORT_FIELDS} }\n lessonProgress { ${LESSON_PROGRESS_FIELDS} }\n`;\n\nconst CERTIFICATE_FIELDS = `\n id workspaceId courseId learnerId enrollmentId serial status issuedAt expiresAt\n revokedAt finalScore createdAt updatedAt\n file { ${FILE_FIELDS} }\n`;\n\n// ============================================================================\n// Shared hook helper: resolve gateway URL from context\n// ============================================================================\n\nconst useGateway = (): string | undefined => {\n const { apiGatewayUrl } = useBuildmyiq();\n return apiGatewayUrl;\n};\n\n// ============================================================================\n// Courses\n// ============================================================================\n\nexport interface UseBuiCoursesArgs {\n level?: BuiCourseLevel;\n status?: BuiCourseStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiCourses = (args: UseBuiCoursesArgs = {}) => {\n const baseUrl = useGateway();\n const { level, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.courses, { level, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListCourses: BuiConnection<BuiCourse> }>(\n `query BuiListCourses($level: BuiCourseLevel, $status: BuiCourseStatus, $pagination: PaginationInput) {\n buiListCourses(level: $level, status: $status, pagination: $pagination) {\n items { ${COURSE_CORE_FIELDS} modules { id } lessons { id } }\n pageInfo { cursor hasMore }\n }\n }`,\n { level, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListCourses;\n },\n });\n};\n\nexport const useBuiCourse = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.course(id) : ['buildmyiq', 'course', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiCourse: BuiCourse | null }>(\n `query BuiCourse($id: ID!) {\n buiCourse(id: $id) { ${COURSE_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiCourse;\n },\n });\n};\n\nexport const useBuiCreateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCourseInput) => {\n const data = await buiQuery<{ buiCreateCourse: BuiCourse }>(\n `mutation BuiCreateCourse($input: BuiCreateCourseInput!) {\n buiCreateCourse(input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCourse;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.courses }),\n });\n};\n\nexport const useBuiUpdateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiUpdateCourseInput }) => {\n const data = await buiQuery<{ buiUpdateCourse: BuiCourse }>(\n `mutation BuiUpdateCourse($id: ID!, $input: BuiUpdateCourseInput!) {\n buiUpdateCourse(id: $id, input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiUpdateCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiPublishCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishCourse: BuiCourse }>(\n `mutation BuiPublishCourse($id: ID!) {\n buiPublishCourse(id: $id) { ${COURSE_CORE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiArchiveCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiArchiveCourse: boolean }>(\n `mutation BuiArchiveCourse($id: ID!) { buiArchiveCourse(id: $id) }`,\n { id },\n baseUrl\n );\n return data.buiArchiveCourse;\n },\n onSuccess: (_r, id) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(id) });\n },\n });\n};\n\n// ============================================================================\n// Modules & lessons\n// ============================================================================\n\nexport const useBuiModules = (courseId: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? KEY.modules(courseId) : ['buildmyiq', 'modules', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListModules: BuiModule[] }>(\n `query BuiListModules($courseId: ID!) {\n buiListModules(courseId: $courseId) { ${MODULE_FIELDS} lessons { ${LESSON_FIELDS} } }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiListModules;\n },\n });\n};\n\nexport const useBuiLessons = (courseId: string | undefined, moduleId?: string) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? [...KEY.lessons(courseId), { moduleId }] : ['buildmyiq', 'lessons', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListLessons: BuiLesson[] }>(\n `query BuiListLessons($courseId: ID!, $moduleId: ID) {\n buiListLessons(courseId: $courseId, moduleId: $moduleId) { ${LESSON_FIELDS} }\n }`,\n { courseId, moduleId },\n baseUrl\n );\n return data.buiListLessons;\n },\n });\n};\n\nexport const useBuiCreateModule = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateModuleInput) => {\n const data = await buiQuery<{ buiCreateModule: BuiModule }>(\n `mutation BuiCreateModule($input: BuiCreateModuleInput!) {\n buiCreateModule(input: $input) { ${MODULE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateModule;\n },\n onSuccess: (m) => {\n qc.invalidateQueries({ queryKey: KEY.modules(m.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(m.courseId) });\n },\n });\n};\n\nexport const useBuiCreateLesson = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateLessonInput) => {\n const data = await buiQuery<{ buiCreateLesson: BuiLesson }>(\n `mutation BuiCreateLesson($input: BuiCreateLessonInput!) {\n buiCreateLesson(input: $input) { ${LESSON_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateLesson;\n },\n onSuccess: (l) => {\n qc.invalidateQueries({ queryKey: KEY.lessons(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.modules(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(l.courseId) });\n },\n });\n};\n\n// ============================================================================\n// Assessments & questions\n// ============================================================================\n\nexport const useBuiAssessments = (courseId: string | undefined, status?: BuiAssessmentStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId\n ? [...KEY.assessments(courseId), { status }]\n : ['buildmyiq', 'assessments', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListAssessments: BuiAssessment[] }>(\n `query BuiListAssessments($courseId: ID!, $status: BuiAssessmentStatus) {\n buiListAssessments(courseId: $courseId, status: $status) { ${ASSESSMENT_FIELDS} questions { id } }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListAssessments;\n },\n });\n};\n\nexport const useBuiAssessment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.assessment(id) : ['buildmyiq', 'assessment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiAssessment: BuiAssessment | null }>(\n `query BuiAssessment($id: ID!) {\n buiAssessment(id: $id) { ${ASSESSMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiAssessment;\n },\n });\n};\n\nexport const useBuiCreateAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateAssessmentInput) => {\n const data = await buiQuery<{ buiCreateAssessment: BuiAssessment }>(\n `mutation BuiCreateAssessment($input: BuiCreateAssessmentInput!) {\n buiCreateAssessment(input: $input) { ${ASSESSMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateAssessment;\n },\n onSuccess: (a) => qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) }),\n });\n};\n\nexport const useBuiPublishAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishAssessment: BuiAssessment }>(\n `mutation BuiPublishAssessment($id: ID!) {\n buiPublishAssessment(id: $id) { ${ASSESSMENT_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishAssessment;\n },\n onSuccess: (a) => {\n qc.invalidateQueries({ queryKey: KEY.assessment(a.id) });\n qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) });\n },\n });\n};\n\nexport const useBuiCreateQuestion = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateQuestionInput) => {\n const data = await buiQuery<{ buiCreateQuestion: BuiQuestion }>(\n `mutation BuiCreateQuestion($input: BuiCreateQuestionInput!) {\n buiCreateQuestion(input: $input) { ${QUESTION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateQuestion;\n },\n onSuccess: (q) => qc.invalidateQueries({ queryKey: KEY.assessment(q.assessmentId) }),\n });\n};\n\n// ============================================================================\n// Submissions & grading\n// ============================================================================\n\nexport interface UseBuiSubmissionsArgs {\n assessmentId?: string;\n learnerId?: string;\n status?: BuiSubmissionStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiSubmissions = (args: UseBuiSubmissionsArgs = {}) => {\n const baseUrl = useGateway();\n const { assessmentId, learnerId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.submissions, { assessmentId, learnerId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListSubmissions: BuiConnection<BuiSubmission> }>(\n `query BuiListSubmissions($assessmentId: ID, $learnerId: ID, $status: BuiSubmissionStatus, $pagination: PaginationInput) {\n buiListSubmissions(assessmentId: $assessmentId, learnerId: $learnerId, status: $status, pagination: $pagination) {\n items { ${SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, learnerId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListSubmissions;\n },\n });\n};\n\nexport const useBuiSubmitAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiSubmitAssessmentInput) => {\n const data = await buiQuery<{ buiSubmitAssessment: BuiSubmission }>(\n `mutation BuiSubmitAssessment($input: BuiSubmitAssessmentInput!) {\n buiSubmitAssessment(input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiSubmitAssessment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\nexport const useBuiGradeSubmission = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiGradeSubmissionInput }) => {\n const data = await buiQuery<{ buiGradeSubmission: BuiSubmission }>(\n `mutation BuiGradeSubmission($id: ID!, $input: BuiGradeSubmissionInput!) {\n buiGradeSubmission(id: $id, input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiGradeSubmission;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\n// ============================================================================\n// Enrollments & progress\n// ============================================================================\n\nexport interface UseBuiEnrollmentsArgs {\n courseId?: string;\n learnerId?: string;\n status?: BuiEnrollmentStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiEnrollments = (args: UseBuiEnrollmentsArgs = {}) => {\n const baseUrl = useGateway();\n const { courseId, learnerId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.enrollments, { courseId, learnerId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListEnrollments: BuiConnection<BuiEnrollment> }>(\n `query BuiListEnrollments($courseId: ID, $learnerId: ID, $status: BuiEnrollmentStatus, $pagination: PaginationInput) {\n buiListEnrollments(courseId: $courseId, learnerId: $learnerId, status: $status, pagination: $pagination) {\n items { ${ENROLLMENT_FIELDS} cohort { ${COHORT_FIELDS} } }\n pageInfo { cursor hasMore }\n }\n }`,\n { courseId, learnerId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListEnrollments;\n },\n });\n};\n\nexport const useBuiEnrollment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.enrollment(id) : ['buildmyiq', 'enrollment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiEnrollment: BuiEnrollment | null }>(\n `query BuiEnrollment($id: ID!) {\n buiEnrollment(id: $id) { ${ENROLLMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiEnrollment;\n },\n });\n};\n\nexport const useBuiEnroll = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiEnrollInput) => {\n const data = await buiQuery<{ buiEnroll: BuiEnrollment }>(\n `mutation BuiEnroll($input: BuiEnrollInput!) {\n buiEnroll(input: $input) { ${ENROLLMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiEnroll;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.enrollments }),\n });\n};\n\nexport const useBuiTransitionEnrollment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; status: BuiEnrollmentStatus }) => {\n const data = await buiQuery<{ buiTransitionEnrollment: BuiEnrollment }>(\n `mutation BuiTransitionEnrollment($id: ID!, $status: BuiEnrollmentStatus!) {\n buiTransitionEnrollment(id: $id, status: $status) { ${ENROLLMENT_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiTransitionEnrollment;\n },\n onSuccess: (e) => {\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n qc.invalidateQueries({ queryKey: KEY.enrollment(e.id) });\n },\n });\n};\n\nexport const useBuiRecordLessonProgress = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiRecordLessonProgressInput) => {\n const data = await buiQuery<{ buiRecordLessonProgress: BuiLessonProgress }>(\n `mutation BuiRecordLessonProgress($input: BuiRecordLessonProgressInput!) {\n buiRecordLessonProgress(input: $input) { ${LESSON_PROGRESS_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiRecordLessonProgress;\n },\n onSuccess: (p) => {\n qc.invalidateQueries({ queryKey: KEY.enrollment(p.enrollmentId) });\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n },\n });\n};\n\n// ============================================================================\n// Cohorts\n// ============================================================================\n\nexport const useBuiCohorts = (courseId?: string, status?: BuiCohortStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.cohorts, { courseId, status }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListCohorts: BuiCohort[] }>(\n `query BuiListCohorts($courseId: ID, $status: BuiCohortStatus) {\n buiListCohorts(courseId: $courseId, status: $status) { ${COHORT_FIELDS} }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListCohorts;\n },\n });\n};\n\nexport const useBuiCreateCohort = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCohortInput) => {\n const data = await buiQuery<{ buiCreateCohort: BuiCohort }>(\n `mutation BuiCreateCohort($input: BuiCreateCohortInput!) {\n buiCreateCohort(input: $input) { ${COHORT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCohort;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.cohorts }),\n });\n};\n\n// ============================================================================\n// Skills\n// ============================================================================\n\nexport const useBuiSkills = (category?: BuiSkillCategory) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.skills, { category }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListSkills: BuiSkill[] }>(\n `query BuiListSkills($category: BuiSkillCategory) {\n buiListSkills(category: $category) { ${SKILL_FIELDS} }\n }`,\n { category },\n baseUrl\n );\n return data.buiListSkills;\n },\n });\n};\n\nexport const useBuiCreateSkill = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateSkillInput) => {\n const data = await buiQuery<{ buiCreateSkill: BuiSkill }>(\n `mutation BuiCreateSkill($input: BuiCreateSkillInput!) {\n buiCreateSkill(input: $input) { ${SKILL_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateSkill;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.skills }),\n });\n};\n\n// ============================================================================\n// Certificates\n// ============================================================================\n\nexport const useBuiCertificates = (courseId?: string, learnerId?: string) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.certificates, { courseId, learnerId }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListCertificates: BuiCertificate[] }>(\n `query BuiListCertificates($courseId: ID, $learnerId: ID) {\n buiListCertificates(courseId: $courseId, learnerId: $learnerId) { ${CERTIFICATE_FIELDS} }\n }`,\n { courseId, learnerId },\n baseUrl\n );\n return data.buiListCertificates;\n },\n });\n};\n\nexport const useBuiIssueCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiIssueCertificateInput) => {\n const data = await buiQuery<{ buiIssueCertificate: BuiCertificate }>(\n `mutation BuiIssueCertificate($input: BuiIssueCertificateInput!) {\n buiIssueCertificate(input: $input) { ${CERTIFICATE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiIssueCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n\nexport const useBuiRevokeCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiRevokeCertificate: BuiCertificate }>(\n `mutation BuiRevokeCertificate($id: ID!) {\n buiRevokeCertificate(id: $id) { ${CERTIFICATE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiRevokeCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ import { Button as x, GlassCard as S } from "@burdenoff/fe-libs/ui";
|
|
|
19
19
|
var C = () => {
|
|
20
20
|
let { currentUser: C, navigateTo: w } = e(), T = C?.id, E = l({
|
|
21
21
|
status: "PUBLISHED",
|
|
22
|
-
limit:
|
|
22
|
+
limit: 50
|
|
23
23
|
}), D = u({
|
|
24
24
|
learnerId: T,
|
|
25
25
|
limit: 50,
|
|
@@ -42,7 +42,7 @@ var C = () => {
|
|
|
42
42
|
children: [
|
|
43
43
|
/* @__PURE__ */ y(n, {
|
|
44
44
|
icon: /* @__PURE__ */ y(h, { className: "h-5 w-5" }),
|
|
45
|
-
value: k.length
|
|
45
|
+
value: `${k.length}${E.data?.pageInfo?.hasMore ? "+" : ""}`,
|
|
46
46
|
label: "Published courses",
|
|
47
47
|
featured: !0
|
|
48
48
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/buildmyiq/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — the Buildmyiq landing surface.\n * @module buildmyiq/pages\n *\n * A learner + educator overview built entirely from real `bui*` data:\n * - KPI row: published courses, my active enrollments, completed, certificates\n * - Continue learning: the caller's in-progress enrollments\n * - Explore: recently published catalog courses\n * Every number and row is derived from live hooks; empty states are honest.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, GraduationCap, CheckCircle2, Award, ArrowRight, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, MetricsCard, EmptyState, LoadingView, Section, MiniBar } from '../components';\nimport { CourseCard } from './_shared';\nimport { useBuildmyiq } from '../context';\nimport { useBuiCourses, useBuiEnrollments, useBuiCertificates } from '../hooks';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { BuiCourse, BuiEnrollment } from '../types';\n\nexport const DashboardPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 8 });\n const enrollmentsQuery = useBuiEnrollments({ learnerId, limit: 50, enabled: Boolean(learnerId) });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const courses: BuiCourse[] = coursesQuery.data?.items ?? [];\n const enrollments: BuiEnrollment[] = enrollmentsQuery.data?.items ?? [];\n const certificates = certsQuery.data ?? [];\n\n const activeEnrollments = useMemo(\n () =>\n enrollments\n .filter((e) => e.status === 'ENROLLED' || e.status === 'IN_PROGRESS')\n .sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n ),\n [enrollments]\n );\n const completedCount = enrollments.filter((e) => e.status === 'COMPLETED').length;\n\n const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'there';\n\n const isLoading = coursesQuery.isLoading || (Boolean(learnerId) && enrollmentsQuery.isLoading);\n\n const courseTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const c of courses) map.set(c.id, c.title);\n return map;\n }, [courses]);\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your learning, teaching and credentialing — all in one workspace.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Loading your learning dashboard…\" />\n ) : (\n <div className=\"flex flex-col gap-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <MetricsCard\n icon={<BookOpen className=\"h-5 w-5\" />}\n value={courses.length}\n label=\"Published courses\"\n featured\n />\n <MetricsCard\n icon={<GraduationCap className=\"h-5 w-5\" />}\n value={activeEnrollments.length}\n label=\"Courses in progress\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={completedCount}\n label=\"Completed\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={certificates.length}\n label=\"Certificates earned\"\n />\n </div>\n\n {/* Continue learning */}\n <Section\n title=\"Continue learning\"\n description=\"Pick up where you left off.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n My learning <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {activeEnrollments.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No active courses yet\"\n description=\"Browse the catalog and enrol in a course to start learning.\"\n action={<Button onClick={() => navigateTo('catalog')}>Explore the catalog</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {activeEnrollments.slice(0, 4).map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <PlayCircle className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium text-text-primary\">\n {courseTitleById.get(e.courseId) ?? 'Course'}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Active {formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}\n </p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Explore catalog */}\n <Section\n title=\"Explore the catalog\"\n description=\"Newly published courses in your workspace.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('catalog')}>\n Browse all <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {courses.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published courses yet\"\n description=\"Create and publish a course in Authoring to populate the catalog.\"\n action={<Button onClick={() => navigateTo('authoring')}>Go to Authoring</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {courses.slice(0, 8).map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onClick={() => navigateTo(`catalog/${course.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/buildmyiq/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — the Buildmyiq landing surface.\n * @module buildmyiq/pages\n *\n * A learner + educator overview built entirely from real `bui*` data:\n * - KPI row: published courses, my active enrollments, completed, certificates\n * - Continue learning: the caller's in-progress enrollments\n * - Explore: recently published catalog courses\n * Every number and row is derived from live hooks; empty states are honest.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, GraduationCap, CheckCircle2, Award, ArrowRight, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, MetricsCard, EmptyState, LoadingView, Section, MiniBar } from '../components';\nimport { CourseCard } from './_shared';\nimport { useBuildmyiq } from '../context';\nimport { useBuiCourses, useBuiEnrollments, useBuiCertificates } from '../hooks';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { BuiCourse, BuiEnrollment } from '../types';\n\nexport const DashboardPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 50 });\n const enrollmentsQuery = useBuiEnrollments({ learnerId, limit: 50, enabled: Boolean(learnerId) });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const courses: BuiCourse[] = coursesQuery.data?.items ?? [];\n const enrollments: BuiEnrollment[] = enrollmentsQuery.data?.items ?? [];\n const certificates = certsQuery.data ?? [];\n\n const activeEnrollments = useMemo(\n () =>\n enrollments\n .filter((e) => e.status === 'ENROLLED' || e.status === 'IN_PROGRESS')\n .sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n ),\n [enrollments]\n );\n const completedCount = enrollments.filter((e) => e.status === 'COMPLETED').length;\n\n const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'there';\n\n const isLoading = coursesQuery.isLoading || (Boolean(learnerId) && enrollmentsQuery.isLoading);\n\n const courseTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const c of courses) map.set(c.id, c.title);\n return map;\n }, [courses]);\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your learning, teaching and credentialing — all in one workspace.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Loading your learning dashboard…\" />\n ) : (\n <div className=\"flex flex-col gap-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <MetricsCard\n icon={<BookOpen className=\"h-5 w-5\" />}\n value={`${courses.length}${coursesQuery.data?.pageInfo?.hasMore ? '+' : ''}`}\n label=\"Published courses\"\n featured\n />\n <MetricsCard\n icon={<GraduationCap className=\"h-5 w-5\" />}\n value={activeEnrollments.length}\n label=\"Courses in progress\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={completedCount}\n label=\"Completed\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={certificates.length}\n label=\"Certificates earned\"\n />\n </div>\n\n {/* Continue learning */}\n <Section\n title=\"Continue learning\"\n description=\"Pick up where you left off.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n My learning <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {activeEnrollments.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No active courses yet\"\n description=\"Browse the catalog and enrol in a course to start learning.\"\n action={<Button onClick={() => navigateTo('catalog')}>Explore the catalog</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {activeEnrollments.slice(0, 4).map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <PlayCircle className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium text-text-primary\">\n {courseTitleById.get(e.courseId) ?? 'Course'}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Active {formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}\n </p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Explore catalog */}\n <Section\n title=\"Explore the catalog\"\n description=\"Newly published courses in your workspace.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('catalog')}>\n Browse all <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {courses.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published courses yet\"\n description=\"Create and publish a course in Authoring to populate the catalog.\"\n action={<Button onClick={() => navigateTo('authoring')}>Go to Authoring</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {courses.slice(0, 8).map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onClick={() => navigateTo(`catalog/${course.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,IAAa,UAA0B;CACrC,IAAM,EAAE,gBAAa,kBAAe,GAAc,EAC5C,IAAY,GAAa,IAEzB,IAAe,EAAc;EAAE,QAAQ;EAAa,OAAO;EAAI,CAAC,EAChE,IAAmB,EAAkB;EAAE;EAAW,OAAO;EAAI,SAAS,EAAQ;EAAY,CAAC,EAC3F,IAAa,EAAmB,KAAA,GAAW,EAAU,EAErD,IAAuB,EAAa,MAAM,SAAS,EAAE,EACrD,IAA+B,EAAiB,MAAM,SAAS,EAAE,EACjE,IAAe,EAAW,QAAQ,EAAE,EAEpC,IAAoB,QAEtB,EACG,QAAQ,MAAM,EAAE,WAAW,cAAc,EAAE,WAAW,cAAc,CACpE,MAAM,GAAG,OACP,EAAE,gBAAgB,EAAE,YAAY,cAAc,EAAE,gBAAgB,EAAE,WAAW,CAC/E,EACL,CAAC,EAAY,CACd,EACK,IAAiB,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAErE,IAAY,GAAa,aAAa,GAAa,MAAM,MAAM,IAAI,CAAC,MAAM,SAE1E,IAAY,EAAa,aAAc,EAAQ,KAAc,EAAiB,WAE9E,IAAkB,QAAc;EACpC,IAAM,oBAAM,IAAI,KAAqB;AACrC,OAAK,IAAM,KAAK,EAAS,GAAI,IAAI,EAAE,IAAI,EAAE,MAAM;AAC/C,SAAO;IACN,CAAC,EAAQ,CAAC;AAEb,QACE,kBAAC,GAAD;EACE,OAAO,iBAAiB;EACxB,aAAY;EACZ,eAAA;EACA,QAAA;YAEC,IACC,kBAAC,GAAD,EAAa,OAAM,oCAAqC,CAAA,GAExD,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAO,GAAG,EAAQ,SAAS,EAAa,MAAM,UAAU,UAAU,MAAM;OACxE,OAAM;OACN,UAAA;OACA,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;OAC3C,OAAO,EAAkB;OACzB,OAAM;OACN,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;OAC1C,OAAO;OACP,OAAM;OACN,QAAO;OACP,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACnC,OAAO,EAAa;OACpB,OAAM;OACN,CAAA;MACE;;IAGN,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,EAAW,WAAW;gBAAvE,CAAyE,gBAC3D,kBAAC,GAAD,EAAY,WAAU,gBAAiB,CAAA,CAC5C;;eAGV,EAAkB,WAAW,IAC5B,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QAAQ,kBAAC,GAAD;OAAQ,eAAe,EAAW,UAAU;iBAAE;OAA4B,CAAA;MAClF,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAkB,MAAM,GAAG,EAAE,CAAC,KAAK,MAClC,kBAAC,GAAD;OAEE,WAAU;OACV,WAAU;OACV,eAAe,EAAW,YAAY,EAAE,KAAK;iBAJ/C,CAME,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAY,WAAU,6BAA8B,CAAA;QAChD,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAgB,IAAI,EAAE,SAAS,IAAI;UAClC,CAAA;SACJ,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAS,OAAO,EAAE;WAAa,WAAU;WAAW,CAAA,EACpD,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACG,EAAE,aAAY,IACV;aACH;;SACN,kBAAC,KAAD;UAAG,WAAU;oBAAb,CAAgD,WACtC,EAAmB,EAAE,gBAAgB,EAAE,WAAW,CACxD;;SACA;UACI;SAtBL,EAAE,GAsBG,CACZ;MACE,CAAA;KAEA,CAAA;IAGV,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,EAAW,UAAU;gBAAtE,CAAwE,eAC3D,kBAAC,GAAD,EAAY,WAAU,gBAAiB,CAAA,CAC3C;;eAGV,EAAQ,WAAW,IAClB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QAAQ,kBAAC,GAAD;OAAQ,eAAe,EAAW,YAAY;iBAAE;OAAwB,CAAA;MAChF,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,MACxB,kBAAC,GAAD;OAEU;OACR,eAAe,EAAW,WAAW,EAAO,KAAK;OACjD,EAHK,EAAO,GAGZ,CACF;MACE,CAAA;KAEA,CAAA;IACN;;EAEG,CAAA"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useBuildmyiq as e } from "../../context/BuildmyiqContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { formatDuration as r } from "../../utils/formatters.js";
|
|
5
|
+
import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
6
|
+
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
7
|
+
import { ErrorView as o, LoadingView as s } from "../../components/StateViews.js";
|
|
8
|
+
import "../../context/index.js";
|
|
9
|
+
import { useBuiAssessment as c, useBuiEnrollments as l, useBuiSubmitAssessment as u } from "../../hooks/useBuildmyiqApi.js";
|
|
10
|
+
import "../../hooks/index.js";
|
|
11
|
+
import "../../components/index.js";
|
|
12
|
+
import { useMemo as d, useState as f } from "react";
|
|
13
|
+
import { ArrowLeft as p, CheckCircle2 as m, ClipboardCheck as h, Send as g } from "lucide-react";
|
|
14
|
+
import { useParams as _ } from "react-router-dom";
|
|
15
|
+
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
16
|
+
import { Button as b, GlassCard as x, Input as S, Textarea as C } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
//#region src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx
|
|
18
|
+
function w(e) {
|
|
19
|
+
return Array.isArray(e) ? e.map((e) => e && typeof e == "object" && "label" in e ? { label: String(e.label) } : null).filter((e) => e !== null && e.label.length > 0) : [];
|
|
20
|
+
}
|
|
21
|
+
var T = [{ label: "True" }, { label: "False" }], E = () => {
|
|
22
|
+
let { assessmentId: E } = _(), { currentUser: D, navigateTo: O } = e(), k = c(E), A = k.data ?? null, j = l({
|
|
23
|
+
learnerId: D?.id,
|
|
24
|
+
courseId: A?.courseId,
|
|
25
|
+
enabled: !!(D?.id && A?.courseId)
|
|
26
|
+
}).data?.items?.[0]?.id, M = u(), [N, P] = f({}), [F, I] = f(null), L = d(() => [...A?.questions ?? []].sort((e, t) => e.position - t.position), [A]), R = (e, t) => {
|
|
27
|
+
P((n) => ({
|
|
28
|
+
...n,
|
|
29
|
+
[e]: t
|
|
30
|
+
}));
|
|
31
|
+
}, z = (e, t) => {
|
|
32
|
+
P((n) => {
|
|
33
|
+
let r = Array.isArray(n[e]) ? n[e] : [];
|
|
34
|
+
return {
|
|
35
|
+
...n,
|
|
36
|
+
[e]: r.includes(t) ? r.filter((e) => e !== t) : [...r, t]
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}, B = L.filter((e) => {
|
|
40
|
+
let t = N[e.id];
|
|
41
|
+
return Array.isArray(t) ? t.length > 0 : !!(t && String(t).trim());
|
|
42
|
+
}).length, V = async () => {
|
|
43
|
+
E && I((await M.mutateAsync({
|
|
44
|
+
assessmentId: E,
|
|
45
|
+
enrollmentId: j,
|
|
46
|
+
attempt: 1,
|
|
47
|
+
answers: N
|
|
48
|
+
})).id);
|
|
49
|
+
}, H = A ? /* @__PURE__ */ y(b, {
|
|
50
|
+
variant: "ghost",
|
|
51
|
+
size: "sm",
|
|
52
|
+
onClick: () => O(`catalog/${A.courseId}`),
|
|
53
|
+
children: [/* @__PURE__ */ v(p, { className: "mr-1 h-4 w-4" }), " Back to course"]
|
|
54
|
+
}) : void 0;
|
|
55
|
+
return E ? k.isLoading ? /* @__PURE__ */ v(n, {
|
|
56
|
+
title: "Assessment",
|
|
57
|
+
children: /* @__PURE__ */ v(s, { label: "Loading the assessment…" })
|
|
58
|
+
}) : k.isError || !A ? /* @__PURE__ */ v(n, {
|
|
59
|
+
title: "Assessment",
|
|
60
|
+
actions: H,
|
|
61
|
+
children: /* @__PURE__ */ v(o, {
|
|
62
|
+
title: "Couldn't load this assessment",
|
|
63
|
+
message: k.error instanceof Error ? k.error.message : void 0,
|
|
64
|
+
onRetry: () => k.refetch()
|
|
65
|
+
})
|
|
66
|
+
}) : A.status === "PUBLISHED" ? F ? /* @__PURE__ */ v(n, {
|
|
67
|
+
title: A.title,
|
|
68
|
+
actions: H,
|
|
69
|
+
children: /* @__PURE__ */ v("div", {
|
|
70
|
+
className: "mx-auto max-w-lg",
|
|
71
|
+
children: /* @__PURE__ */ y(x, {
|
|
72
|
+
treatment: "glass",
|
|
73
|
+
glow: !0,
|
|
74
|
+
className: "flex flex-col items-center gap-4 p-8 text-center",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ v("div", {
|
|
77
|
+
className: "flex h-14 w-14 items-center justify-center rounded-full bg-status-success-bg-subtle",
|
|
78
|
+
children: /* @__PURE__ */ v(m, { className: "h-7 w-7 text-status-success-text" })
|
|
79
|
+
}),
|
|
80
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h2", {
|
|
81
|
+
className: "text-lg font-semibold text-text-primary",
|
|
82
|
+
children: "Submitted"
|
|
83
|
+
}), /* @__PURE__ */ y("p", {
|
|
84
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
85
|
+
children: [
|
|
86
|
+
"Your answers for “",
|
|
87
|
+
A.title,
|
|
88
|
+
"” were submitted.",
|
|
89
|
+
A.isGraded ? " An educator will grade them — you’ll see your score and feedback in My Learning." : " Thanks for completing it."
|
|
90
|
+
]
|
|
91
|
+
})] }),
|
|
92
|
+
/* @__PURE__ */ y("div", {
|
|
93
|
+
className: "flex flex-wrap justify-center gap-2",
|
|
94
|
+
children: [/* @__PURE__ */ v(b, {
|
|
95
|
+
variant: "secondary",
|
|
96
|
+
onClick: () => O("learning"),
|
|
97
|
+
children: "Go to My Learning"
|
|
98
|
+
}), /* @__PURE__ */ v(b, {
|
|
99
|
+
variant: "ghost",
|
|
100
|
+
onClick: () => O(`catalog/${A.courseId}`),
|
|
101
|
+
children: "Back to course"
|
|
102
|
+
})]
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
}) : /* @__PURE__ */ v(n, {
|
|
108
|
+
title: A.title,
|
|
109
|
+
description: A.description ?? void 0,
|
|
110
|
+
actions: H,
|
|
111
|
+
children: /* @__PURE__ */ y("div", {
|
|
112
|
+
className: "mx-auto max-w-3xl",
|
|
113
|
+
children: [/* @__PURE__ */ y("div", {
|
|
114
|
+
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ v(i, { status: A.type }),
|
|
117
|
+
/* @__PURE__ */ y("span", {
|
|
118
|
+
className: "text-sm text-text-secondary",
|
|
119
|
+
children: [
|
|
120
|
+
"Passing score ",
|
|
121
|
+
A.passingScore,
|
|
122
|
+
"%"
|
|
123
|
+
]
|
|
124
|
+
}),
|
|
125
|
+
A.timeLimitMin > 0 && /* @__PURE__ */ y("span", {
|
|
126
|
+
className: "text-sm text-text-secondary",
|
|
127
|
+
children: ["· ", r(A.timeLimitMin)]
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ y("span", {
|
|
130
|
+
className: "text-sm text-text-secondary",
|
|
131
|
+
children: [
|
|
132
|
+
"· ",
|
|
133
|
+
B,
|
|
134
|
+
"/",
|
|
135
|
+
L.length,
|
|
136
|
+
" answered"
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
}), L.length === 0 ? /* @__PURE__ */ v(a, {
|
|
141
|
+
icon: /* @__PURE__ */ v(h, { className: "h-8 w-8" }),
|
|
142
|
+
title: "No questions yet",
|
|
143
|
+
description: "This assessment doesn't have any questions to answer."
|
|
144
|
+
}) : /* @__PURE__ */ y("div", {
|
|
145
|
+
className: "flex flex-col gap-4",
|
|
146
|
+
children: [
|
|
147
|
+
L.map((e, n) => {
|
|
148
|
+
let r = e.type === "TRUE_FALSE" ? T : w(e.options), i = N[e.id];
|
|
149
|
+
return /* @__PURE__ */ v(x, {
|
|
150
|
+
treatment: "plain",
|
|
151
|
+
className: "p-5",
|
|
152
|
+
children: /* @__PURE__ */ y("div", {
|
|
153
|
+
className: "flex items-start gap-3",
|
|
154
|
+
children: [/* @__PURE__ */ v("span", {
|
|
155
|
+
className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary tabular-nums",
|
|
156
|
+
children: n + 1
|
|
157
|
+
}), /* @__PURE__ */ y("div", {
|
|
158
|
+
className: "min-w-0 flex-1",
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ v("p", {
|
|
161
|
+
className: "font-medium text-text-primary",
|
|
162
|
+
children: e.prompt
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ y("p", {
|
|
165
|
+
className: "mt-0.5 text-xs text-text-secondary",
|
|
166
|
+
children: [
|
|
167
|
+
e.points,
|
|
168
|
+
" ",
|
|
169
|
+
e.points === 1 ? "point" : "points"
|
|
170
|
+
]
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ y("div", {
|
|
173
|
+
className: "mt-4",
|
|
174
|
+
children: [
|
|
175
|
+
(e.type === "SINGLE_CHOICE" || e.type === "TRUE_FALSE") && /* @__PURE__ */ v("div", {
|
|
176
|
+
className: "flex flex-col gap-2",
|
|
177
|
+
children: r.map((n) => /* @__PURE__ */ y("label", {
|
|
178
|
+
className: t("flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors", i === n.label ? "border-accent bg-[var(--color-accent-soft)] text-text-primary" : "border-border-seam text-text-secondary hover:bg-bg-sunken"),
|
|
179
|
+
children: [/* @__PURE__ */ v("input", {
|
|
180
|
+
type: "radio",
|
|
181
|
+
name: e.id,
|
|
182
|
+
className: "accent-[var(--color-accent-primary)]",
|
|
183
|
+
checked: i === n.label,
|
|
184
|
+
onChange: () => R(e.id, n.label)
|
|
185
|
+
}), n.label]
|
|
186
|
+
}, n.label))
|
|
187
|
+
}),
|
|
188
|
+
e.type === "MULTIPLE_CHOICE" && /* @__PURE__ */ v("div", {
|
|
189
|
+
className: "flex flex-col gap-2",
|
|
190
|
+
children: r.map((n) => {
|
|
191
|
+
let r = Array.isArray(i) && i.includes(n.label);
|
|
192
|
+
return /* @__PURE__ */ y("label", {
|
|
193
|
+
className: t("flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors", r ? "border-accent bg-[var(--color-accent-soft)] text-text-primary" : "border-border-seam text-text-secondary hover:bg-bg-sunken"),
|
|
194
|
+
children: [/* @__PURE__ */ v("input", {
|
|
195
|
+
type: "checkbox",
|
|
196
|
+
className: "accent-[var(--color-accent-primary)]",
|
|
197
|
+
checked: r,
|
|
198
|
+
onChange: () => z(e.id, n.label)
|
|
199
|
+
}), n.label]
|
|
200
|
+
}, n.label);
|
|
201
|
+
})
|
|
202
|
+
}),
|
|
203
|
+
e.type === "SHORT_ANSWER" && /* @__PURE__ */ v(S, {
|
|
204
|
+
value: typeof i == "string" ? i : "",
|
|
205
|
+
onChange: (t) => R(e.id, t.target.value),
|
|
206
|
+
placeholder: "Your answer"
|
|
207
|
+
}),
|
|
208
|
+
(e.type === "ESSAY" || e.type === "CODE") && /* @__PURE__ */ v(C, {
|
|
209
|
+
rows: e.type === "CODE" ? 6 : 4,
|
|
210
|
+
value: typeof i == "string" ? i : "",
|
|
211
|
+
onChange: (t) => R(e.id, t.target.value),
|
|
212
|
+
placeholder: e.type === "CODE" ? "Write your code…" : "Write your answer…",
|
|
213
|
+
className: e.type === "CODE" ? "font-mono text-sm" : void 0
|
|
214
|
+
})
|
|
215
|
+
]
|
|
216
|
+
})
|
|
217
|
+
]
|
|
218
|
+
})]
|
|
219
|
+
})
|
|
220
|
+
}, e.id);
|
|
221
|
+
}),
|
|
222
|
+
M.isError && /* @__PURE__ */ v("p", {
|
|
223
|
+
className: "text-sm text-status-error-text",
|
|
224
|
+
children: M.error instanceof Error ? M.error.message : "Could not submit your answers."
|
|
225
|
+
}),
|
|
226
|
+
/* @__PURE__ */ y("div", {
|
|
227
|
+
className: "sticky bottom-0 flex items-center justify-between gap-3 border-t border-border-seam bg-bg-surface/90 py-3 backdrop-blur",
|
|
228
|
+
children: [/* @__PURE__ */ y("span", {
|
|
229
|
+
className: "text-sm text-text-secondary tabular-nums",
|
|
230
|
+
children: [
|
|
231
|
+
B,
|
|
232
|
+
" of ",
|
|
233
|
+
L.length,
|
|
234
|
+
" answered"
|
|
235
|
+
]
|
|
236
|
+
}), /* @__PURE__ */ y(b, {
|
|
237
|
+
onClick: V,
|
|
238
|
+
disabled: M.isPending || B === 0,
|
|
239
|
+
children: [/* @__PURE__ */ v(g, { className: "mr-1 h-4 w-4" }), M.isPending ? "Submitting…" : "Submit assessment"]
|
|
240
|
+
})]
|
|
241
|
+
})
|
|
242
|
+
]
|
|
243
|
+
})]
|
|
244
|
+
})
|
|
245
|
+
}) : /* @__PURE__ */ v(n, {
|
|
246
|
+
title: A.title,
|
|
247
|
+
actions: H,
|
|
248
|
+
children: /* @__PURE__ */ v(a, {
|
|
249
|
+
illustration: "empty-data",
|
|
250
|
+
title: "This assessment isn't open yet",
|
|
251
|
+
description: "It has to be published by an educator before you can take it."
|
|
252
|
+
})
|
|
253
|
+
}) : /* @__PURE__ */ v(n, {
|
|
254
|
+
title: "Assessment",
|
|
255
|
+
children: /* @__PURE__ */ v(a, {
|
|
256
|
+
illustration: "empty-data",
|
|
257
|
+
title: "No assessment selected"
|
|
258
|
+
})
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
//#endregion
|
|
262
|
+
export { E as TakeAssessmentPage };
|
|
263
|
+
|
|
264
|
+
//# sourceMappingURL=TakeAssessmentPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TakeAssessmentPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx"],"sourcesContent":["/**\n * TakeAssessmentPage — the LEARNER-facing surface to attempt a published\n * assessment and submit answers.\n * @module buildmyiq/pages/assessments\n *\n * Loads the assessment + its questions, collects an answer per question (typed\n * to the question kind), resolves the caller's enrollment for the assessment's\n * course, and submits via `buiSubmitAssessment` ({ assessmentId, enrollmentId?,\n * answers }). Grading is done by an educator in the Grading queue — this page is\n * honest about that (\"submitted, awaiting grading\"). All data is live.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, CheckCircle2, ClipboardCheck, Send } from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n LoadingView,\n ErrorView,\n EmptyState,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiAssessment, useBuiEnrollments, useBuiSubmitAssessment } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport { formatDuration } from '../../utils/formatters';\nimport type { BuiQuestion } from '../../types';\n\ninterface Choice {\n label: string;\n}\n\n/** Narrow the untyped `options` JSON blob to a list of `{ label }` choices. */\nfunction parseChoices(options: unknown): Choice[] {\n if (!Array.isArray(options)) return [];\n return options\n .map((o) => (o && typeof o === 'object' && 'label' in o ? { label: String((o as { label: unknown }).label) } : null))\n .filter((c): c is Choice => c !== null && c.label.length > 0);\n}\n\ntype AnswerValue = string | string[];\n\nconst TRUE_FALSE_CHOICES: Choice[] = [{ label: 'True' }, { label: 'False' }];\n\nexport const TakeAssessmentPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { currentUser, navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const assessment = assessmentQuery.data ?? null;\n\n const enrollmentsQuery = useBuiEnrollments({\n learnerId: currentUser?.id,\n courseId: assessment?.courseId,\n enabled: Boolean(currentUser?.id && assessment?.courseId),\n });\n const enrollmentId = enrollmentsQuery.data?.items?.[0]?.id;\n\n const submit = useBuiSubmitAssessment();\n const [answers, setAnswers] = useState<Record<string, AnswerValue>>({});\n const [submittedId, setSubmittedId] = useState<string | null>(null);\n\n const questions = useMemo<BuiQuestion[]>(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const setAnswer = (qid: string, value: AnswerValue): void => {\n setAnswers((prev) => ({ ...prev, [qid]: value }));\n };\n\n const toggleMulti = (qid: string, label: string): void => {\n setAnswers((prev) => {\n const cur = Array.isArray(prev[qid]) ? (prev[qid] as string[]) : [];\n return { ...prev, [qid]: cur.includes(label) ? cur.filter((l) => l !== label) : [...cur, label] };\n });\n };\n\n const answeredCount = questions.filter((q) => {\n const a = answers[q.id];\n return Array.isArray(a) ? a.length > 0 : Boolean(a && String(a).trim());\n }).length;\n\n const handleSubmit = async (): Promise<void> => {\n if (!assessmentId) return;\n const result = await submit.mutateAsync({\n assessmentId,\n enrollmentId,\n attempt: 1,\n answers,\n });\n setSubmittedId(result.id);\n };\n\n const backToCourse = assessment\n ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" /> Back to course\n </Button>\n )\n : undefined;\n\n if (!assessmentId) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState illustration=\"empty-data\" title=\"No assessment selected\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading the assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError || !assessment) {\n return (\n <PageLayout title=\"Assessment\" actions={backToCourse}>\n <ErrorView\n title=\"Couldn't load this assessment\"\n message={assessmentQuery.error instanceof Error ? assessmentQuery.error.message : undefined}\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (assessment.status !== 'PUBLISHED') {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <EmptyState\n illustration=\"empty-data\"\n title=\"This assessment isn't open yet\"\n description=\"It has to be published by an educator before you can take it.\"\n />\n </PageLayout>\n );\n }\n\n // Submitted confirmation\n if (submittedId) {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <div className=\"mx-auto max-w-lg\">\n <GlassCard treatment=\"glass\" glow className=\"flex flex-col items-center gap-4 p-8 text-center\">\n <div className=\"flex h-14 w-14 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <CheckCircle2 className=\"h-7 w-7 text-status-success-text\" />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Submitted</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Your answers for “{assessment.title}” were submitted.\n {assessment.isGraded\n ? ' An educator will grade them — you’ll see your score and feedback in My Learning.'\n : ' Thanks for completing it.'}\n </p>\n </div>\n <div className=\"flex flex-wrap justify-center gap-2\">\n <Button variant=\"secondary\" onClick={() => navigateTo('learning')}>\n Go to My Learning\n </Button>\n <Button variant=\"ghost\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n Back to course\n </Button>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={assessment.title}\n description={assessment.description ?? undefined}\n actions={backToCourse}\n >\n <div className=\"mx-auto max-w-3xl\">\n {/* Meta strip */}\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <StatusBadge status={assessment.type} />\n <span className=\"text-sm text-text-secondary\">Passing score {assessment.passingScore}%</span>\n {assessment.timeLimitMin > 0 && (\n <span className=\"text-sm text-text-secondary\">· {formatDuration(assessment.timeLimitMin)}</span>\n )}\n <span className=\"text-sm text-text-secondary\">\n · {answeredCount}/{questions.length} answered\n </span>\n </div>\n\n {questions.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck className=\"h-8 w-8\" />}\n title=\"No questions yet\"\n description=\"This assessment doesn't have any questions to answer.\"\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {questions.map((q, idx) => {\n const choices =\n q.type === 'TRUE_FALSE' ? TRUE_FALSE_CHOICES : parseChoices(q.options);\n const answer = answers[q.id];\n return (\n <GlassCard key={q.id} treatment=\"plain\" className=\"p-5\">\n <div className=\"flex items-start gap-3\">\n <span className=\"mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary tabular-nums\">\n {idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary\">{q.prompt}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {q.points} {q.points === 1 ? 'point' : 'points'}\n </p>\n\n <div className=\"mt-4\">\n {(q.type === 'SINGLE_CHOICE' || q.type === 'TRUE_FALSE') && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => (\n <label\n key={c.label}\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors',\n answer === c.label\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"radio\"\n name={q.id}\n className=\"accent-[var(--color-accent-primary)]\"\n checked={answer === c.label}\n onChange={() => setAnswer(q.id, c.label)}\n />\n {c.label}\n </label>\n ))}\n </div>\n )}\n\n {q.type === 'MULTIPLE_CHOICE' && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => {\n const checked = Array.isArray(answer) && answer.includes(c.label);\n return (\n <label\n key={c.label}\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2 text-sm transition-colors',\n checked\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"checkbox\"\n className=\"accent-[var(--color-accent-primary)]\"\n checked={checked}\n onChange={() => toggleMulti(q.id, c.label)}\n />\n {c.label}\n </label>\n );\n })}\n </div>\n )}\n\n {q.type === 'SHORT_ANSWER' && (\n <Input\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder=\"Your answer\"\n />\n )}\n\n {(q.type === 'ESSAY' || q.type === 'CODE') && (\n <Textarea\n rows={q.type === 'CODE' ? 6 : 4}\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder={q.type === 'CODE' ? 'Write your code…' : 'Write your answer…'}\n className={q.type === 'CODE' ? 'font-mono text-sm' : undefined}\n />\n )}\n </div>\n </div>\n </div>\n </GlassCard>\n );\n })}\n\n {submit.isError && (\n <p className=\"text-sm text-status-error-text\">\n {submit.error instanceof Error ? submit.error.message : 'Could not submit your answers.'}\n </p>\n )}\n\n <div className=\"sticky bottom-0 flex items-center justify-between gap-3 border-t border-border-seam bg-bg-surface/90 py-3 backdrop-blur\">\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {answeredCount} of {questions.length} answered\n </span>\n <Button onClick={handleSubmit} disabled={submit.isPending || answeredCount === 0}>\n <Send className=\"mr-1 h-4 w-4\" />\n {submit.isPending ? 'Submitting…' : 'Submit assessment'}\n </Button>\n </div>\n </div>\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -216,15 +216,15 @@ var k = [
|
|
|
216
216
|
}), M.isLoading ? /* @__PURE__ */ C(a, { label: "Loading courses…" }) : M.isError ? /* @__PURE__ */ C(i, {
|
|
217
217
|
message: "We couldn't load your courses.",
|
|
218
218
|
onRetry: () => M.refetch()
|
|
219
|
-
}) : P.length === 0 ? /* @__PURE__ */ C(r, {
|
|
219
|
+
}) : /* @__PURE__ */ w(S, { children: [P.length === 0 ? /* @__PURE__ */ C(r, {
|
|
220
220
|
illustration: "empty-data",
|
|
221
221
|
title: I,
|
|
222
|
-
description: "Create your first course to start building curriculum, assessments and cohorts.",
|
|
222
|
+
description: g ? "None of the courses on this page are authored by you — clear \"Mine only\" or page through to find them." : "Create your first course to start building curriculum, assessments and cohorts.",
|
|
223
223
|
action: /* @__PURE__ */ w(T, {
|
|
224
224
|
onClick: () => E(!0),
|
|
225
225
|
children: [/* @__PURE__ */ C(x, { className: "mr-1.5 h-4 w-4" }), " New course"]
|
|
226
226
|
})
|
|
227
|
-
}) : /* @__PURE__ */
|
|
227
|
+
}) : /* @__PURE__ */ C("div", {
|
|
228
228
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
229
229
|
children: P.map((e) => /* @__PURE__ */ C(f, {
|
|
230
230
|
course: e,
|
|
@@ -234,7 +234,7 @@ var k = [
|
|
|
234
234
|
}), /* @__PURE__ */ C(s, {
|
|
235
235
|
page: O.page,
|
|
236
236
|
pageSize: A,
|
|
237
|
-
itemCount:
|
|
237
|
+
itemCount: P.length,
|
|
238
238
|
hasMore: M.data?.pageInfo.hasMore ?? !1,
|
|
239
239
|
onPageChange: (e) => O.onPageChange(e, M.data?.pageInfo.cursor)
|
|
240
240
|
})] })]
|