Files
classroomio/apps/api/src/services/course/utils.ts
T

160 lines
4.3 KiB
TypeScript
Raw Normal View History

2026-03-09 11:18:22 +01:00
import { ContentType } from '@cio/utils/constants';
import type { CourseContentItemRow } from '@cio/db/queries/course';
export type CourseContentItem = {
id: string;
type: ContentType;
title: string;
order: number | null;
createdAt: string | null;
sectionId: string | null;
isUnlocked: boolean | null;
isComplete: boolean | null;
lessonAt: string | null;
callUrl: string | null;
hasNoteContent: boolean | null;
hasSlideContent: boolean | null;
videosCount: number | null;
documentsCount: number | null;
questionCount: number | null;
dueBy: string | null;
};
export type CourseContentSection = {
id: string;
title: string;
order: number | null;
items: CourseContentItem[];
};
export type CourseContent = {
grouped: boolean;
sections: CourseContentSection[];
items: CourseContentItem[];
};
const CONTENT_TYPE_PRIORITY: Record<ContentType, number> = {
[ContentType.Section]: -1,
[ContentType.Lesson]: 0,
[ContentType.Exercise]: 1
};
function sortContentItems(items: CourseContentItem[]): CourseContentItem[] {
return [...items].sort((a, b) => {
const orderDiff = (a.order ?? 0) - (b.order ?? 0);
if (orderDiff !== 0) return orderDiff;
const typeDiff = CONTENT_TYPE_PRIORITY[a.type] - CONTENT_TYPE_PRIORITY[b.type];
if (typeDiff !== 0) return typeDiff;
return new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime();
});
}
function sortContentSections(sections: CourseContentSection[]): CourseContentSection[] {
return [...sections].sort((a, b) => {
const orderDiff = (a.order ?? 0) - (b.order ?? 0);
if (orderDiff !== 0) return orderDiff;
return a.title.localeCompare(b.title);
});
}
function mapCourseContentItems(rows: CourseContentItemRow[]): CourseContentItem[] {
return rows.map((row) => ({
id: row.id,
type: row.type as ContentType,
title: row.title ?? '',
order: row.order ?? null,
createdAt: row.createdAt ?? null,
sectionId: row.sectionId ?? null,
isUnlocked: row.isUnlocked ?? null,
isComplete: row.isComplete ?? null,
lessonAt: row.lessonAt ?? null,
callUrl: row.callUrl ?? null,
hasNoteContent: row.hasNoteContent ?? null,
hasSlideContent: row.hasSlideContent ?? null,
videosCount: row.videosCount ?? null,
documentsCount: row.documentsCount ?? null,
questionCount: row.questionCount ?? null,
dueBy: row.dueBy ?? null
}));
}
export function buildCourseContent(rows: CourseContentItemRow[], isContentGroupingEnabled: boolean): CourseContent {
const items = mapCourseContentItems(rows);
const contentItems = items.filter((item) => item.type !== ContentType.Section);
if (!isContentGroupingEnabled) {
return {
grouped: false,
sections: [],
items: sortContentItems(contentItems)
};
}
const sectionItems = items.filter((item) => item.type === ContentType.Section);
const sectionMap = new Map<string, CourseContentItem[]>();
const ungroupedItems: CourseContentItem[] = [];
for (const item of contentItems) {
if (!item.sectionId) {
ungroupedItems.push(item);
continue;
}
if (!sectionMap.has(item.sectionId)) {
sectionMap.set(item.sectionId, []);
}
sectionMap.get(item.sectionId)!.push(item);
}
const sections = sortContentSections(
sectionItems.map((section) => ({
id: section.id,
title: section.title || 'Untitled section',
order: section.order ?? null,
items: sortContentItems(sectionMap.get(section.id) ?? [])
}))
);
if (ungroupedItems.length > 0) {
sections.push({
id: 'ungrouped',
title: 'Ungrouped',
order: sections.length + 1,
items: sortContentItems(ungroupedItems)
});
}
return {
grouped: true,
sections,
items: []
};
}
export function calcPercentageWithRounding(a: number, b: number): number {
if (b === 0) return 0;
return Math.round((a / b) * 100);
}
export function formatLastSeen(lastLoginDate: string | null | undefined): string {
if (!lastLoginDate) return 'Never';
const lastLogin = new Date(lastLoginDate);
const now = new Date();
const diffInHours = Math.floor((now.getTime() - lastLogin.getTime()) / (1000 * 60 * 60));
if (diffInHours < 1) {
return 'Just now';
}
if (diffInHours < 24) {
return `${diffInHours} hours ago`;
}
const diffInDays = Math.floor(diffInHours / 24);
return `${diffInDays} days ago`;
}