2023-09-27 04:23:45 +01:00
|
|
|
<script lang="ts">
|
2025-11-21 12:18:04 +01:00
|
|
|
import * as Table from '@cio/ui/base/table';
|
2025-12-02 09:54:38 +01:00
|
|
|
import { Empty } from '@cio/ui/custom/empty';
|
|
|
|
|
import { Search } from '@cio/ui/custom/search';
|
|
|
|
|
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
|
|
|
|
import * as Page from '@cio/ui/base/page';
|
|
|
|
|
import * as Select from '@cio/ui/base/select';
|
|
|
|
|
import GridIcon from '@lucide/svelte/icons/grid-2x2';
|
|
|
|
|
import ListIcon from '@lucide/svelte/icons/list';
|
2025-12-22 02:36:48 +01:00
|
|
|
import { IconButton } from '@cio/ui/custom/icon-button';
|
2025-12-02 09:54:38 +01:00
|
|
|
|
|
|
|
|
import {
|
2025-12-22 02:36:48 +01:00
|
|
|
CourseCardList,
|
2025-12-02 09:54:38 +01:00
|
|
|
CourseCardLoader,
|
|
|
|
|
CreateCourseButton,
|
|
|
|
|
CourseList,
|
|
|
|
|
CopyCourseModal,
|
|
|
|
|
NewCourseModal
|
2025-12-22 02:36:48 +01:00
|
|
|
} from '$features/course/components';
|
2025-11-21 12:18:04 +01:00
|
|
|
|
2025-12-22 02:36:48 +01:00
|
|
|
import { DeleteModal } from '$features/ui';
|
2023-09-27 04:23:45 +01:00
|
|
|
import type { Course } from '$lib/utils/types';
|
2024-04-19 05:45:48 +01:00
|
|
|
import { t } from '$lib/utils/functions/translations';
|
2025-12-22 02:36:48 +01:00
|
|
|
import { snackbar } from '$features/ui/snackbar/store';
|
2025-11-21 12:18:04 +01:00
|
|
|
import { deleteCourse } from '$lib/utils/services/courses';
|
2025-12-02 09:54:38 +01:00
|
|
|
import {
|
|
|
|
|
deleteCourseModal,
|
|
|
|
|
deleteCourseModalInitialState,
|
|
|
|
|
courses as coursesStore,
|
|
|
|
|
courseMetaDeta
|
|
|
|
|
} from '../utils/store';
|
|
|
|
|
import { browser } from '$app/environment';
|
2025-11-01 15:49:29 +01:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
courses?: Course[];
|
2025-12-22 02:36:48 +01:00
|
|
|
emptyTitle?: string;
|
|
|
|
|
emptyDescription?: string;
|
2025-11-01 15:49:29 +01:00
|
|
|
isExplore?: boolean;
|
2025-12-22 02:36:48 +01:00
|
|
|
isLMS?: boolean;
|
2025-12-02 09:54:38 +01:00
|
|
|
searchValue?: string;
|
|
|
|
|
selectedId?: string;
|
2025-11-01 15:49:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let {
|
|
|
|
|
courses = [],
|
|
|
|
|
emptyTitle = $t('courses.course_card.empty_title'),
|
|
|
|
|
emptyDescription = $t('courses.course_card.empty_description'),
|
2025-12-02 09:54:38 +01:00
|
|
|
isExplore = false,
|
2025-12-22 02:36:48 +01:00
|
|
|
isLMS = false,
|
2025-12-02 09:54:38 +01:00
|
|
|
searchValue = $bindable(''),
|
|
|
|
|
selectedId = $bindable('0')
|
2025-11-01 15:49:29 +01:00
|
|
|
}: Props = $props();
|
2023-09-28 11:45:50 +01:00
|
|
|
|
2025-12-02 09:54:38 +01:00
|
|
|
const filterOptions = $derived([
|
|
|
|
|
{ value: '0', label: $t('courses.course_filter.date_created') },
|
|
|
|
|
{ value: '1', label: $t('courses.course_filter.published') },
|
|
|
|
|
{ value: '2', label: $t('courses.course_filter.lessons') }
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const selectedLabel = $derived(
|
|
|
|
|
filterOptions.find((opt) => opt.value === selectedId)?.label || filterOptions[0].label
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setViewPreference = (preference: 'grid' | 'list') => {
|
|
|
|
|
$courseMetaDeta.view = preference;
|
|
|
|
|
if (browser) {
|
|
|
|
|
localStorage.setItem('courseView', preference);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-29 09:57:29 +01:00
|
|
|
async function handleDeleteCourse() {
|
|
|
|
|
if (!$deleteCourseModal.id) return;
|
|
|
|
|
|
|
|
|
|
$deleteCourseModal.isDeleting = true;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await deleteCourse($deleteCourseModal.id);
|
2025-11-01 16:18:29 +01:00
|
|
|
|
2025-10-29 09:57:29 +01:00
|
|
|
// Remove the course from the courses store
|
2025-11-01 16:18:29 +01:00
|
|
|
$coursesStore = $coursesStore.filter((course) => course.id !== $deleteCourseModal.id);
|
|
|
|
|
|
2025-10-29 09:57:29 +01:00
|
|
|
// Show success message
|
|
|
|
|
snackbar.success('snackbar.course_deleted');
|
2025-11-01 16:18:29 +01:00
|
|
|
|
2025-10-29 09:57:29 +01:00
|
|
|
// Close modal and reset state
|
|
|
|
|
deleteCourseModal.set(deleteCourseModalInitialState);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error deleting course:', error);
|
|
|
|
|
snackbar.error('snackbar.course_settings.error.went_wrong');
|
2025-11-01 16:18:29 +01:00
|
|
|
|
2025-10-29 09:57:29 +01:00
|
|
|
// Stop deleting state on error
|
|
|
|
|
$deleteCourseModal.isDeleting = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-06 08:22:25 +03:00
|
|
|
</script>
|
|
|
|
|
|
2025-12-02 09:54:38 +01:00
|
|
|
<NewCourseModal />
|
|
|
|
|
|
2025-10-29 09:57:29 +01:00
|
|
|
<CopyCourseModal />
|
2025-12-02 09:54:38 +01:00
|
|
|
|
2025-11-01 16:18:29 +01:00
|
|
|
<DeleteModal
|
|
|
|
|
onDelete={handleDeleteCourse}
|
2025-10-29 09:57:29 +01:00
|
|
|
bind:open={$deleteCourseModal.open}
|
|
|
|
|
isLoading={$deleteCourseModal.isDeleting}
|
|
|
|
|
/>
|
2021-12-30 22:30:30 +02:00
|
|
|
|
2025-12-02 09:54:38 +01:00
|
|
|
<Page.BodyHeader align="right">
|
|
|
|
|
<Search placeholder={$t('courses.search_placeholder')} bind:value={searchValue} />
|
|
|
|
|
<Select.Root type="single" bind:value={selectedId}>
|
|
|
|
|
<Select.Trigger class="min-w-[150px]">
|
|
|
|
|
<p>{selectedId ? selectedLabel : filterOptions[0].label}</p>
|
|
|
|
|
</Select.Trigger>
|
|
|
|
|
<Select.Content>
|
2025-12-22 02:36:48 +01:00
|
|
|
{#each filterOptions as option (option.label)}
|
2025-12-02 09:54:38 +01:00
|
|
|
<Select.Item value={option.value}>{option.label}</Select.Item>
|
|
|
|
|
{/each}
|
|
|
|
|
</Select.Content>
|
|
|
|
|
</Select.Root>
|
|
|
|
|
{#if $courseMetaDeta.view === 'list'}
|
2025-12-22 02:36:48 +01:00
|
|
|
<IconButton onclick={() => setViewPreference('grid')}>
|
2025-12-02 09:54:38 +01:00
|
|
|
<GridIcon size={16} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
{:else}
|
2025-12-22 02:36:48 +01:00
|
|
|
<IconButton onclick={() => setViewPreference('list')}>
|
2025-12-02 09:54:38 +01:00
|
|
|
<ListIcon size={16} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
{/if}
|
|
|
|
|
</Page.BodyHeader>
|
|
|
|
|
|
|
|
|
|
<div class="mx-auto w-full flex-1">
|
2021-12-30 22:30:30 +02:00
|
|
|
{#if $courseMetaDeta.isLoading}
|
2024-04-20 14:40:44 +01:00
|
|
|
<section class={`${$courseMetaDeta.isLoading || courses ? 'cards-container' : ''} `}>
|
2025-12-02 09:54:38 +01:00
|
|
|
<CourseCardLoader />
|
|
|
|
|
<CourseCardLoader />
|
|
|
|
|
<CourseCardLoader />
|
2024-04-20 14:40:44 +01:00
|
|
|
</section>
|
2025-12-02 09:54:38 +01:00
|
|
|
{:else if !courses.length}
|
|
|
|
|
<Empty title={emptyTitle} description={emptyDescription} icon={LibraryBigIcon} variant="page">
|
2025-12-22 02:36:48 +01:00
|
|
|
{#if !isLMS}
|
|
|
|
|
<CreateCourseButton isResponsive />
|
|
|
|
|
{/if}
|
2025-12-02 09:54:38 +01:00
|
|
|
</Empty>
|
2024-04-20 14:40:44 +01:00
|
|
|
{:else if $courseMetaDeta.view === 'list'}
|
2025-11-21 12:18:04 +01:00
|
|
|
<div class="w-full overflow-hidden rounded-md border">
|
|
|
|
|
<Table.Root class="w-full table-fixed">
|
|
|
|
|
<Table.Header>
|
|
|
|
|
<Table.Row>
|
|
|
|
|
<Table.Head class="w-[20%]">{$t('courses.course_card.list_view.title')}</Table.Head>
|
|
|
|
|
<Table.Head class="w-[30%]">{$t('courses.course_card.list_view.description')}</Table.Head>
|
|
|
|
|
<Table.Head class="w-[10%]">{$t('courses.course_card.list_view.type')}</Table.Head>
|
|
|
|
|
<Table.Head class="w-[10%]">{$t('courses.course_card.list_view.lessons')}</Table.Head>
|
|
|
|
|
<Table.Head class="w-[10%]">{$t('courses.course_card.list_view.students')}</Table.Head>
|
|
|
|
|
<Table.Head class="w-[12%]">{$t('courses.course_card.list_view.published')}</Table.Head>
|
|
|
|
|
<Table.Head class="w-[8%]"></Table.Head>
|
|
|
|
|
</Table.Row>
|
|
|
|
|
</Table.Header>
|
|
|
|
|
<Table.Body>
|
2025-12-22 02:36:48 +01:00
|
|
|
{#each courses as courseData (courseData.id)}
|
2025-12-02 09:54:38 +01:00
|
|
|
<CourseList
|
2025-11-21 12:18:04 +01:00
|
|
|
id={courseData.id}
|
|
|
|
|
title={courseData.title}
|
|
|
|
|
type={$t(`course.navItem.settings.${courseData.type.toLowerCase()}`)}
|
|
|
|
|
description={courseData.description}
|
|
|
|
|
isPublished={courseData.is_published}
|
|
|
|
|
totalLessons={courseData.total_lessons}
|
|
|
|
|
totalStudents={courseData.total_students}
|
|
|
|
|
/>
|
|
|
|
|
{/each}
|
|
|
|
|
</Table.Body>
|
|
|
|
|
</Table.Root>
|
|
|
|
|
</div>
|
2021-12-30 22:30:30 +02:00
|
|
|
{:else}
|
2025-12-22 02:36:48 +01:00
|
|
|
<CourseCardList {courses} {isExplore} {isLMS} />
|
2021-12-30 22:30:30 +02:00
|
|
|
{/if}
|
2021-05-07 03:43:37 +03:00
|
|
|
</div>
|