Files
classroomio/apps/dashboard/src/lib/features/course/pages/lessons.svelte
T

122 lines
4.7 KiB
Svelte

<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { Empty } from '@cio/ui/custom/empty';
import BookOpenIcon from '@lucide/svelte/icons/book-open';
import ContentList from '$features/course/components/lesson/content-list.svelte';
import ContentSectionList from '$features/course/components/lesson/content-section-list.svelte';
import CourseContentIcon from '$features/course/components/course-content-icon.svelte';
import { courseApi } from '$features/course/api';
import { t } from '$lib/utils/functions/translations';
import { getCourseContent } from '$features/course/utils/content';
import { ContentType } from '@cio/utils/constants/content';
interface Props {
courseId: string;
reorder?: boolean;
}
let { courseId, reorder = $bindable(false) }: Props = $props();
const query = new URLSearchParams(page.url.search);
const contentData = $derived(getCourseContent(courseApi.course));
const contentLength = $derived(contentData.grouped ? contentData.sections.length : contentData.items.length);
const contentItems = $derived(
contentData.grouped ? contentData.sections.flatMap((section) => section.items) : contentData.items
);
const navigableContentItems = $derived(
contentItems.filter((item) => item.type === ContentType.Lesson || item.type === ContentType.Exercise)
);
const sectionsTotal = $derived(
contentData.grouped ? contentData.sections.filter((section) => section.id !== 'ungrouped').length : 0
);
const lessonsTotal = $derived(contentItems.filter((item) => item.type === ContentType.Lesson).length);
const exercisesTotal = $derived(contentItems.filter((item) => item.type === ContentType.Exercise).length);
let isFetching: boolean = $state(false);
let hasHandledNext = $state(false);
const isCourseLoadedForThisPage = $derived(courseApi.course?.id === courseId);
const canResolveNext = $derived(isCourseLoadedForThisPage && navigableContentItems.length > 0 && !hasHandledNext);
function findFirstIncompleteContent() {
return navigableContentItems.find((item) => !item.isComplete && item.isUnlocked === true);
}
$effect(() => {
if (!canResolveNext || isFetching || query.get('next') !== 'true') return;
hasHandledNext = true;
const incompleteContent = findFirstIncompleteContent();
if (incompleteContent) {
if (incompleteContent.type === ContentType.Lesson) {
goto(`/courses/${courseId}/lessons/${incompleteContent.id}`);
} else {
goto(`/courses/${courseId}/exercises/${incompleteContent.id}`);
}
} else {
goto(`/courses/${courseId}/lessons`);
}
});
const shouldShowNextPlaceholder = $derived(query.get('next') === 'true');
</script>
{#if shouldShowNextPlaceholder}
<Empty
title={$t('course.navItem.lessons.no_lesson')}
description={$t('course.navItem.lessons.share_your_knowledge')}
icon={BookOpenIcon}
variant="page"
/>
{:else if contentLength > 0}
<div
class="mb-5 grid grid-cols-1 gap-3 sm:grid-cols-3"
role="region"
aria-label={t.get('course.navItem.lessons.heading_v2')}
>
<div class="ui:border-border flex flex-col gap-1 rounded-lg border px-4 py-3">
<div class="ui:text-muted-foreground flex items-center gap-2 text-xs font-medium">
<CourseContentIcon type={ContentType.Section} size={14} />
<span>{$t('course.navItem.lessons.stats.sections')}</span>
</div>
<p class="text-2xl font-semibold tabular-nums">{sectionsTotal}</p>
</div>
<div class="ui:border-border flex flex-col gap-1 rounded-lg border px-4 py-3">
<div class="ui:text-muted-foreground flex items-center gap-2 text-xs font-medium">
<CourseContentIcon type={ContentType.Lesson} size={14} />
<span>{$t('course.navItem.lessons.stats.lessons')}</span>
</div>
<p class="text-2xl font-semibold tabular-nums">{lessonsTotal}</p>
</div>
<div class="ui:border-border flex flex-col gap-1 rounded-lg border px-4 py-3">
<div class="ui:text-muted-foreground flex items-center gap-2 text-xs font-medium">
<CourseContentIcon type={ContentType.Exercise} size={14} />
<span>{$t('course.navItem.lessons.stats.exercises')}</span>
</div>
<p class="text-2xl font-semibold tabular-nums">{exercisesTotal}</p>
</div>
</div>
{#if reorder}
<p class="text-center text-xs text-gray-400 italic dark:text-white">
{$t('course.navItem.lessons.drag')}
</p>
{/if}
{#if contentData.grouped}
<ContentSectionList {reorder} />
{:else}
<ContentList {reorder} />
{/if}
{:else}
<Empty
title={$t('course.navItem.lessons.body_header')}
description={$t('course.navItem.lessons.body_content')}
icon={BookOpenIcon}
variant="page"
/>
{/if}