fix(dashboard): hide compliance in-progress badge and enforce deadline on view-site publish (#913)
* fix(dashboard): hide in-progress badge on compliance learner cards Keep the due date on My Learning and LMS current-course cards, and only show compliance status badges for states that need attention. Co-authored-by: best <[email protected]> * fix(dashboard): enforce compliance deadline when publishing from view course site Reuse the settings deadline-required dialog and navigate to the completion deadline section so unpublished-site publish matches settings and the API. Co-authored-by: best <[email protected]> * fix(dashboard): keep attention highlight visible after deadline navigation Prevent goto and dialog focus restoration from scrolling away from the completion deadline section before the highlight pulse can render. Co-authored-by: best <[email protected]> * fix(dashboard): keep deadline attention highlight visible longer Use onMount plus afterNavigate so the completion deadline ring is not lost to navigation scroll races, and show a stronger primary ring for five seconds. Co-authored-by: best <[email protected]> * test(dashboard): use Jest APIs in compliance badge tests Drop the Vitest import so the new test matches the dashboard's declared Jest runner and @types/jest globals. Co-authored-by: best <[email protected]> ---------
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { isPublishedComplianceMissingDeadline } from '@cio/utils/functions';
|
||||
import { goAndHighlight } from '$lib/routing/go-and-highlight';
|
||||
import { ROUTE_NAME, ROUTE_SECTIONS } from '$lib/routing/routes';
|
||||
import type { Course } from './types';
|
||||
|
||||
type CourseDeadlineSource = Pick<Course, 'type'> & {
|
||||
certificate?: { deadline?: string | null } | null;
|
||||
};
|
||||
|
||||
export function isCourseMissingComplianceDeadline(course: CourseDeadlineSource): boolean {
|
||||
return isPublishedComplianceMissingDeadline({
|
||||
type: course.type,
|
||||
isPublished: true,
|
||||
deadline: course.certificate?.deadline
|
||||
});
|
||||
}
|
||||
|
||||
export function goToCourseCompletionDeadline(courseId: string) {
|
||||
goAndHighlight(ROUTE_NAME.COURSE_SETTINGS, ROUTE_SECTIONS[ROUTE_NAME.COURSE_SETTINGS].COMPLETION_DEADLINE, {
|
||||
id: courseId
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { shouldShowStudentCourseComplianceStatusBadge, getStudentCourseComplianceStatusKey } from './compliance-utils';
|
||||
|
||||
function makeCourse(overrides: { type?: string; complianceStatus?: string | null }) {
|
||||
return {
|
||||
type: overrides.type ?? 'COMPLIANCE',
|
||||
complianceStatus: overrides.complianceStatus
|
||||
} as Parameters<typeof shouldShowStudentCourseComplianceStatusBadge>[0];
|
||||
}
|
||||
|
||||
describe('shouldShowStudentCourseComplianceStatusBadge', () => {
|
||||
it('hides the in-progress badge on learner course cards', () => {
|
||||
const course = makeCourse({ complianceStatus: 'in_progress' });
|
||||
|
||||
expect(shouldShowStudentCourseComplianceStatusBadge(course)).toBe(false);
|
||||
expect(getStudentCourseComplianceStatusKey(course)).toBe('course.navItem.compliance.status.in_progress');
|
||||
});
|
||||
|
||||
it('still shows overdue and expiring statuses', () => {
|
||||
expect(shouldShowStudentCourseComplianceStatusBadge(makeCourse({ complianceStatus: 'non_compliant' }))).toBe(true);
|
||||
expect(shouldShowStudentCourseComplianceStatusBadge(makeCourse({ complianceStatus: 'expiring_soon' }))).toBe(true);
|
||||
});
|
||||
|
||||
it('is hidden for non-compliance courses', () => {
|
||||
expect(
|
||||
shouldShowStudentCourseComplianceStatusBadge(makeCourse({ type: 'SELF_PACED', complianceStatus: 'in_progress' }))
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -98,6 +98,12 @@ export function getStudentCourseComplianceStatusKey(course: StudentEnrolledCours
|
||||
return complianceStatus ? `course.navItem.compliance.status.${complianceStatus}` : null;
|
||||
}
|
||||
|
||||
export function shouldShowStudentCourseComplianceStatusBadge(course: StudentEnrolledCourse) {
|
||||
const complianceStatus = getStudentCourseComplianceStatus(course);
|
||||
|
||||
return !!complianceStatus && complianceStatus !== 'in_progress';
|
||||
}
|
||||
|
||||
export function getStudentCourseComplianceStatusVariant(course: StudentEnrolledCourse) {
|
||||
const complianceStatus = getStudentCourseComplianceStatus(course);
|
||||
if (!complianceStatus) {
|
||||
|
||||
@@ -2,10 +2,17 @@ import { generateSlug } from '@cio/utils/functions';
|
||||
import { isObject } from '$lib/utils/functions/isObject';
|
||||
import { courseApi } from '$features/course/api';
|
||||
import type { Course } from '$features/course/utils/types';
|
||||
import { isCourseMissingComplianceDeadline } from './compliance-deadline';
|
||||
|
||||
export async function publishCourse(course: Course): Promise<boolean> {
|
||||
export type PublishCourseResult = { ok: true } | { ok: false; reason: 'missing_deadline' | 'failed' };
|
||||
|
||||
export async function publishCourse(course: Course): Promise<PublishCourseResult> {
|
||||
if (!course?.id) {
|
||||
return false;
|
||||
return { ok: false, reason: 'failed' };
|
||||
}
|
||||
|
||||
if (isCourseMissingComplianceDeadline(course)) {
|
||||
return { ok: false, reason: 'missing_deadline' };
|
||||
}
|
||||
|
||||
let slug = course.slug?.trim() ? course.slug : undefined;
|
||||
@@ -28,5 +35,13 @@ export async function publishCourse(course: Course): Promise<boolean> {
|
||||
{ showSuccessToast: true }
|
||||
);
|
||||
|
||||
return !!result;
|
||||
if (!result) {
|
||||
if (courseApi.errors['certificate.deadline']) {
|
||||
return { ok: false, reason: 'missing_deadline' };
|
||||
}
|
||||
|
||||
return { ok: false, reason: 'failed' };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user