CLASSROOMIO-T-108: Revamp landing page & sidebar improvement
This commit is contained in:
+9
-4
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
|
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||||
|
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
||||||
|
|
||||||
function isNoteEmpty(note) {
|
function isNoteEmpty(note: string) {
|
||||||
if (!note || typeof note !== 'string') return true;
|
if (!note || typeof note !== 'string') return true;
|
||||||
|
|
||||||
if (!document) return false;
|
if (!document) return false;
|
||||||
@@ -16,7 +17,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !isNoteEmpty($lesson.materials?.note)}
|
{#if !isNoteEmpty($lesson.materials?.note)}
|
||||||
<div>
|
<HtmlRender>
|
||||||
{@html $lesson.materials.note}
|
<svelte:fragment slot="content">
|
||||||
</div>
|
<div>
|
||||||
|
{@html $lesson.materials.note}
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
</HtmlRender>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
import AlignBoxTopLeftIcon from 'carbon-icons-svelte/lib/AlignBoxTopLeft.svelte';
|
import AlignBoxTopLeftIcon from 'carbon-icons-svelte/lib/AlignBoxTopLeft.svelte';
|
||||||
import ListIcon from 'carbon-icons-svelte/lib/List.svelte';
|
import ListIcon from 'carbon-icons-svelte/lib/List.svelte';
|
||||||
import IbmWatsonKnowledgeStudioIcon from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeStudio.svelte';
|
import IbmWatsonKnowledgeStudioIcon from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeStudio.svelte';
|
||||||
import MachineLearningModel from 'carbon-icons-svelte/lib/MachineLearningModel.svelte';
|
|
||||||
import MagicWandFilled from 'carbon-icons-svelte/lib/MagicWandFilled.svelte';
|
import MagicWandFilled from 'carbon-icons-svelte/lib/MagicWandFilled.svelte';
|
||||||
import Tabs from '$lib/components/Tabs/index.svelte';
|
import Tabs from '$lib/components/Tabs/index.svelte';
|
||||||
import TabContent from '$lib/components/TabContent/index.svelte';
|
import TabContent from '$lib/components/TabContent/index.svelte';
|
||||||
@@ -126,7 +125,7 @@
|
|||||||
api: '/api/completion'
|
api: '/api/completion'
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateNoteByCompletion(completion) {
|
function updateNoteByCompletion(completion: string) {
|
||||||
$lesson.materials.note = completion;
|
$lesson.materials.note = completion;
|
||||||
|
|
||||||
autoSave($lesson.materials);
|
autoSave($lesson.materials);
|
||||||
@@ -240,143 +239,143 @@
|
|||||||
<h1 class="text-4xl mt-0 capitalize">
|
<h1 class="text-4xl mt-0 capitalize">
|
||||||
{lessonTitle}
|
{lessonTitle}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{#if mode === MODES.edit}
|
|
||||||
<Tabs {tabs} {currentTab} {onChange}>
|
|
||||||
<slot:fragment slot="content">
|
|
||||||
<TabContent value={getValue('Note')} index={currentTab}>
|
|
||||||
<div bind:this={aiButtonRef} class="w-full flex flex-row-reverse">
|
|
||||||
<PrimaryButton
|
|
||||||
className="flex items-center relative"
|
|
||||||
onClick={() => {
|
|
||||||
openPopover = !openPopover;
|
|
||||||
}}
|
|
||||||
isLoading={$isLoading}
|
|
||||||
isDisabled={$isLoading}
|
|
||||||
variant={VARIANTS.OUTLINED}
|
|
||||||
>
|
|
||||||
<MagicWandFilled size={20} class="carbon-icon mr-3" />
|
|
||||||
AI
|
|
||||||
<Popover
|
|
||||||
caret
|
|
||||||
align="left"
|
|
||||||
bind:open={openPopover}
|
|
||||||
on:click:outside={({ detail }) => {
|
|
||||||
openPopover = aiButtonRef?.contains(detail.target);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="p-2">
|
|
||||||
<button class={aiButtonClass} on:click={() => callAI('outline')}>
|
|
||||||
<ListIcon class="carbon-icon mr-2" />
|
|
||||||
Generate Lesson Outline
|
|
||||||
</button>
|
|
||||||
<button class={aiButtonClass} on:click={() => callAI('note')}>
|
|
||||||
<AlignBoxTopLeftIcon class="carbon-icon mr-2" />
|
|
||||||
Generate Lesson Note
|
|
||||||
</button>
|
|
||||||
<button class={aiButtonClass} on:click={() => callAI('activities')}>
|
|
||||||
<IbmWatsonKnowledgeStudioIcon class="carbon-icon mr-2" />
|
|
||||||
Generate Lesson Activities
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</PrimaryButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-[60vh] mt-5">
|
|
||||||
<TextEditor
|
|
||||||
id={lessonId}
|
|
||||||
bind:editorWindowRef
|
|
||||||
value={$lesson.materials.note}
|
|
||||||
onChange={(html) => ($lesson.materials.note = html)}
|
|
||||||
placeholder="Write your lesson note here"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabContent>
|
|
||||||
|
|
||||||
<TabContent value={getValue('Slide')} index={currentTab}>
|
|
||||||
{#if mode === MODES.edit}
|
|
||||||
<TextField
|
|
||||||
label="Slide link"
|
|
||||||
bind:value={$lesson.materials.slide_url}
|
|
||||||
onInputChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</TabContent>
|
|
||||||
<TabContent value={getValue('Video')} index={currentTab}>
|
|
||||||
<PrimaryButton label="Add/Edit Video(s)" onClick={openAddVideoModal} className="mb-2" />
|
|
||||||
{#if $lesson.materials.videos.length}
|
|
||||||
<div class="flex flex-col items-start w-full h-full">
|
|
||||||
{#each $lesson.materials.videos as video, index}
|
|
||||||
{#if mode === MODES.edit}
|
|
||||||
<div class="ml-auto">
|
|
||||||
<IconButton
|
|
||||||
value="delete-video"
|
|
||||||
contained={true}
|
|
||||||
onClick={() => deleteLessonVideo(index)}
|
|
||||||
>
|
|
||||||
<TrashCanIcon size={20} class="carbon-icon dark:text-white" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
|
|
||||||
{#key video.link}
|
|
||||||
<div class="mb-5">
|
|
||||||
{#if video.type === 'youtube'}
|
|
||||||
<iframe
|
|
||||||
width="100%"
|
|
||||||
height="569"
|
|
||||||
class="iframe"
|
|
||||||
src={formatYoutubeVideo(video.link, errors)}
|
|
||||||
title="YouTube video player"
|
|
||||||
frameborder="0"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
||||||
allowfullscreen
|
|
||||||
/>
|
|
||||||
{:else if video.metadata?.svid}
|
|
||||||
<div style="position:relative;padding-bottom:51.416579%">
|
|
||||||
<iframe
|
|
||||||
src="https://muse.ai/embed/{video.metadata
|
|
||||||
?.svid}?logo=https://app.classroomio.com/logo-512.png&subtitles=auto&cover_play_position=center"
|
|
||||||
style="width:100%;height:100%;position:absolute;left:0;top:0"
|
|
||||||
frameborder="0"
|
|
||||||
allowfullscreen
|
|
||||||
title="Muse AI Video Embed"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<video bind:this={player} class="plyr-video-trigger" playsinline controls>
|
|
||||||
<source src={video.link} type="video/mp4" />
|
|
||||||
<track kind="captions" />
|
|
||||||
</video>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</TabContent>
|
|
||||||
</slot:fragment>
|
|
||||||
</Tabs>
|
|
||||||
{:else if !isMaterialsEmpty($lesson.materials)}
|
|
||||||
<div class="w-full">
|
|
||||||
{#each componentsToRender as Component, index}
|
|
||||||
<svelte:component this={Component} key={index} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<Box className="text-center">
|
|
||||||
<img src="/no-video.svg" alt="Video not found" />
|
|
||||||
<h3 class="text-xl font-normal dark:text-white py-2">
|
|
||||||
No note, video or slide added for this lesson yet
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-center font-normal py-2">
|
|
||||||
Share your knowledge with your students by creating engaging content<br />
|
|
||||||
Start by clicking on <strong>Get started</strong> button.
|
|
||||||
</p>
|
|
||||||
<PrimaryButton label="Get started" className="rounded-md" onClick={toggleMode} />
|
|
||||||
</Box>
|
|
||||||
{/if}
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</HtmlRender>
|
</HtmlRender>
|
||||||
|
|
||||||
|
{#if mode === MODES.edit}
|
||||||
|
<Tabs {tabs} {currentTab} {onChange}>
|
||||||
|
<slot:fragment slot="content">
|
||||||
|
<TabContent value={getValue('Note')} index={currentTab}>
|
||||||
|
<div bind:this={aiButtonRef} class="w-full flex flex-row-reverse">
|
||||||
|
<PrimaryButton
|
||||||
|
className="flex items-center relative"
|
||||||
|
onClick={() => {
|
||||||
|
openPopover = !openPopover;
|
||||||
|
}}
|
||||||
|
isLoading={$isLoading}
|
||||||
|
isDisabled={$isLoading}
|
||||||
|
variant={VARIANTS.OUTLINED}
|
||||||
|
>
|
||||||
|
<MagicWandFilled size={20} class="carbon-icon mr-3" />
|
||||||
|
AI
|
||||||
|
<Popover
|
||||||
|
caret
|
||||||
|
align="left"
|
||||||
|
bind:open={openPopover}
|
||||||
|
on:click:outside={({ detail }) => {
|
||||||
|
openPopover = aiButtonRef?.contains(detail.target);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="p-2">
|
||||||
|
<button class={aiButtonClass} on:click={() => callAI('outline')}>
|
||||||
|
<ListIcon class="carbon-icon mr-2" />
|
||||||
|
Generate Lesson Outline
|
||||||
|
</button>
|
||||||
|
<button class={aiButtonClass} on:click={() => callAI('note')}>
|
||||||
|
<AlignBoxTopLeftIcon class="carbon-icon mr-2" />
|
||||||
|
Generate Lesson Note
|
||||||
|
</button>
|
||||||
|
<button class={aiButtonClass} on:click={() => callAI('activities')}>
|
||||||
|
<IbmWatsonKnowledgeStudioIcon class="carbon-icon mr-2" />
|
||||||
|
Generate Lesson Activities
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</PrimaryButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-[60vh] mt-5">
|
||||||
|
<TextEditor
|
||||||
|
id={lessonId}
|
||||||
|
bind:editorWindowRef
|
||||||
|
value={$lesson.materials.note}
|
||||||
|
onChange={(html) => ($lesson.materials.note = html)}
|
||||||
|
placeholder="Write your lesson note here"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabContent>
|
||||||
|
|
||||||
|
<TabContent value={getValue('Slide')} index={currentTab}>
|
||||||
|
{#if mode === MODES.edit}
|
||||||
|
<TextField
|
||||||
|
label="Slide link"
|
||||||
|
bind:value={$lesson.materials.slide_url}
|
||||||
|
onInputChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</TabContent>
|
||||||
|
<TabContent value={getValue('Video')} index={currentTab}>
|
||||||
|
<PrimaryButton label="Add/Edit Video(s)" onClick={openAddVideoModal} className="mb-2" />
|
||||||
|
{#if $lesson.materials.videos.length}
|
||||||
|
<div class="flex flex-col items-start w-full h-full">
|
||||||
|
{#each $lesson.materials.videos as video, index}
|
||||||
|
{#if mode === MODES.edit}
|
||||||
|
<div class="ml-auto">
|
||||||
|
<IconButton
|
||||||
|
value="delete-video"
|
||||||
|
contained={true}
|
||||||
|
onClick={() => deleteLessonVideo(index)}
|
||||||
|
>
|
||||||
|
<TrashCanIcon size={20} class="carbon-icon dark:text-white" />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
|
||||||
|
{#key video.link}
|
||||||
|
<div class="mb-5">
|
||||||
|
{#if video.type === 'youtube'}
|
||||||
|
<iframe
|
||||||
|
width="100%"
|
||||||
|
height="569"
|
||||||
|
class="iframe"
|
||||||
|
src={formatYoutubeVideo(video.link, errors)}
|
||||||
|
title="YouTube video player"
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen
|
||||||
|
/>
|
||||||
|
{:else if video.metadata?.svid}
|
||||||
|
<div style="position:relative;padding-bottom:51.416579%">
|
||||||
|
<iframe
|
||||||
|
src="https://muse.ai/embed/{video.metadata
|
||||||
|
?.svid}?logo=https://app.classroomio.com/logo-512.png&subtitles=auto&cover_play_position=center"
|
||||||
|
style="width:100%;height:100%;position:absolute;left:0;top:0"
|
||||||
|
frameborder="0"
|
||||||
|
allowfullscreen
|
||||||
|
title="Muse AI Video Embed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<video bind:this={player} class="plyr-video-trigger" playsinline controls>
|
||||||
|
<source src={video.link} type="video/mp4" />
|
||||||
|
<track kind="captions" />
|
||||||
|
</video>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</TabContent>
|
||||||
|
</slot:fragment>
|
||||||
|
</Tabs>
|
||||||
|
{:else if !isMaterialsEmpty($lesson.materials)}
|
||||||
|
<div class="w-full">
|
||||||
|
{#each componentsToRender as Component, index}
|
||||||
|
<svelte:component this={Component} key={index} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Box className="text-center">
|
||||||
|
<img src="/no-video.svg" alt="Video not found" />
|
||||||
|
<h3 class="text-xl font-normal dark:text-white py-2">
|
||||||
|
No note, video or slide added for this lesson yet
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-center font-normal py-2">
|
||||||
|
Share your knowledge with your students by creating engaging content<br />
|
||||||
|
Start by clicking on <strong>Get started</strong> button.
|
||||||
|
</p>
|
||||||
|
<PrimaryButton label="Get started" className="rounded-md" onClick={toggleMode} />
|
||||||
|
</Box>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@
|
|||||||
<a
|
<a
|
||||||
href="{$page.url.pathname}{navItem.key}"
|
href="{$page.url.pathname}{navItem.key}"
|
||||||
class="{navItem.key === activeNav &&
|
class="{navItem.key === activeNav &&
|
||||||
'active text-primary-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-neutral-600 hover:text-slate-900 dark:text-white z-0"
|
'active text-primary-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-neutral-600 dark:hover:text-slate-900 dark:text-white z-0"
|
||||||
>
|
>
|
||||||
{navItem.label}
|
{navItem.label}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
import Card from './components/Card/index.svelte';
|
import Card from './components/Card/index.svelte';
|
||||||
import CardLoader from './components/Card/Loader.svelte';
|
import CardLoader from './components/Card/Loader.svelte';
|
||||||
import CoursesEmptyIcon from '../Icons/CoursesEmptyIcon.svelte';
|
import CoursesEmptyIcon from '../Icons/CoursesEmptyIcon.svelte';
|
||||||
// import CopyCourseModal from './components/CopyCourseModal/index.svelte';
|
|
||||||
import { courseMetaDeta } from './store';
|
import { courseMetaDeta } from './store';
|
||||||
import type { Course } from '$lib/utils/types';
|
import type { Course } from '$lib/utils/types';
|
||||||
import { appStore } from '$lib/utils/store/app';
|
import { appStore } from '$lib/utils/store/app';
|
||||||
|
|
||||||
export let courses: Course[] = [];
|
export let courses: Course[] = [];
|
||||||
|
export let emptyTitle = 'No Courses Created';
|
||||||
|
export let emptyDescription =
|
||||||
|
'Share your knowledge with the world by creating engaging courses for your students.';
|
||||||
|
|
||||||
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
|
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
|
||||||
if (!progressRate || !totalLessons) {
|
if (!progressRate || !totalLessons) {
|
||||||
@@ -34,7 +36,6 @@
|
|||||||
bannerImage={courseData.logo || '/images/classroomio-course-img-template.jpg'}
|
bannerImage={courseData.logo || '/images/classroomio-course-img-template.jpg'}
|
||||||
title={courseData.title}
|
title={courseData.title}
|
||||||
description={courseData.description}
|
description={courseData.description}
|
||||||
role_id={courseData.role_id}
|
|
||||||
isPublished={courseData.is_published}
|
isPublished={courseData.is_published}
|
||||||
cost={courseData.cost}
|
cost={courseData.cost}
|
||||||
currency={courseData.currency}
|
currency={courseData.currency}
|
||||||
@@ -49,9 +50,9 @@
|
|||||||
{#if !$courseMetaDeta.isLoading && !courses.length}
|
{#if !$courseMetaDeta.isLoading && !courses.length}
|
||||||
<Box className="w-full">
|
<Box className="w-full">
|
||||||
<CoursesEmptyIcon />
|
<CoursesEmptyIcon />
|
||||||
<h3 class="dark:text-white text-2xl my-5">No Courses Created</h3>
|
<h3 class="dark:text-white text-2xl my-5">{emptyTitle}</h3>
|
||||||
<p class="dark:text-white w-1/3 text-center">
|
<p class="dark:text-white w-1/3 text-center">
|
||||||
Share your knowledge with the world by creating engaging courses for your students.
|
{emptyDescription}
|
||||||
</p>
|
</p>
|
||||||
</Box>
|
</Box>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,9 +2,16 @@ import { writable, derived } from 'svelte/store';
|
|||||||
import type { Course } from '$lib/utils/types';
|
import type { Course } from '$lib/utils/types';
|
||||||
|
|
||||||
export const courses = writable<Course[]>([]);
|
export const courses = writable<Course[]>([]);
|
||||||
export const courseInProgress = derived(courses, ($courses) =>
|
export const coursesInProgress = derived(courses, ($courses) =>
|
||||||
$courses.length > 0 ? $courses.slice(0, 3) : []
|
$courses.length > 0 ? $courses.slice(0, 3) : []
|
||||||
);
|
);
|
||||||
|
export const coursesComplete = derived(courses, ($courses) =>
|
||||||
|
$courses.length > 0
|
||||||
|
? $courses.filter((course) => {
|
||||||
|
return course.total_lessons === course.progress_rate;
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
);
|
||||||
export const courseMetaDeta = writable({
|
export const courseMetaDeta = writable({
|
||||||
isLoading: true
|
isLoading: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
|
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
|
||||||
import LicenseDraft from 'carbon-icons-svelte/lib/LicenseDraft.svelte';
|
import LicenseDraft from 'carbon-icons-svelte/lib/LicenseDraft.svelte';
|
||||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
|
||||||
import OrgSelector from '$lib/components/OrgSelector/OrgSelector.svelte';
|
|
||||||
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
|
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
|
||||||
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
|
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
|
||||||
import CommunityIcon from '$lib/components/Icons/CommunityIcon.svelte';
|
import CommunityIcon from '$lib/components/Icons/CommunityIcon.svelte';
|
||||||
@@ -59,19 +58,21 @@
|
|||||||
>
|
>
|
||||||
<div class="h-full flex flex-col">
|
<div class="h-full flex flex-col">
|
||||||
<div class="border-b border-gray-200 pt-5 px-4">
|
<div class="border-b border-gray-200 pt-5 px-4">
|
||||||
{#if $currentOrg.avatar_url}
|
<div class="w-full flex flex-col items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={$currentOrg.avatar_url}
|
src={$profile.avatar_url}
|
||||||
name={$currentOrg.name}
|
name={$profile.fullname}
|
||||||
shape="rounded-md"
|
shape="rounded-full"
|
||||||
width="w-7"
|
width="w-20"
|
||||||
height="h-7"
|
height="h-20"
|
||||||
/>
|
/>
|
||||||
{:else}
|
|
||||||
<TextChip value={$currentOrg.shortName} className="bg-primary-200 font-bold" />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<OrgSelector canAddOrg={false} />
|
<div class="mt-5">
|
||||||
|
<p class="dark:text-white text-lg font-bold whitespace-nowrap truncate">
|
||||||
|
{$profile.fullname}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="my-5">
|
<ul class="my-5">
|
||||||
{#each sideLinks as item (item.name)}
|
{#each sideLinks as item (item.name)}
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
`/lms/settings`
|
`/lms/settings`
|
||||||
) && NavClasses.active}"
|
) && NavClasses.active}"
|
||||||
>
|
>
|
||||||
<Avatar src={$profile.avatar_url} name={$profile.username} width="w-7" height="h-7" />
|
<Settings size={20} class="carbon-icon dark:text-white" />
|
||||||
<p class="dark:text-white ml-2">Settings</p>
|
<p class="dark:text-white ml-2">Settings</p>
|
||||||
</li>
|
</li>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
|
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
|
||||||
import { courseInProgress } from '$lib/components/Courses/store';
|
import { coursesInProgress } from '$lib/components/Courses/store';
|
||||||
|
|
||||||
const gotoCourse = (id: string | undefined) => {
|
const gotoCourse = (id: string | undefined) => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="h-full mt-10 mb-20 xl:mt-2">
|
<section class="h-full">
|
||||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Currently Learning</p>
|
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Currently Learning</p>
|
||||||
<div
|
<div
|
||||||
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded w-full h-fit xl:h-[516px] p-3"
|
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded w-full lg:h-[40vh] p-3"
|
||||||
>
|
>
|
||||||
{#if $courseInProgress.length > 0}
|
{#if $coursesInProgress.length > 0}
|
||||||
<div class="w-full h-full xl:h-[516px] flex flex-col justify-start">
|
<div class="w-full h-full flex flex-col justify-start overflow-y-auto">
|
||||||
{#each $courseInProgress as course}
|
{#each $coursesInProgress as course}
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<span class="flex flex-col md:flex-row gap-3 items-start pb-5">
|
<span class="flex flex-col md:flex-row gap-3 items-start pb-5">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Your Schedule</p>
|
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Your Schedule</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 rounded w-full h-fit xl:h-[516px] p-3"
|
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 rounded w-full h-fit lg:h-[40vh] p-3 overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div class="w-full h-fit hidden xl:flex items-center justify-center">
|
<div class="w-full h-fit hidden xl:flex items-center justify-center">
|
||||||
<InlineCalendar bind:store {theme} />
|
<InlineCalendar bind:store {theme} />
|
||||||
|
|||||||
@@ -4,11 +4,14 @@
|
|||||||
import NotificationIcon from 'carbon-icons-svelte/lib/Notification.svelte';
|
import NotificationIcon from 'carbon-icons-svelte/lib/Notification.svelte';
|
||||||
import MoonIcon from 'carbon-icons-svelte/lib/Moon.svelte';
|
import MoonIcon from 'carbon-icons-svelte/lib/Moon.svelte';
|
||||||
import SunIcon from 'carbon-icons-svelte/lib/Sun.svelte';
|
import SunIcon from 'carbon-icons-svelte/lib/Sun.svelte';
|
||||||
|
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||||
|
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||||
|
|
||||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||||
import { appStore } from '$lib/utils/store/app';
|
import { appStore } from '$lib/utils/store/app';
|
||||||
import { Close, Menu } from 'carbon-icons-svelte';
|
import { Close, Menu } from 'carbon-icons-svelte';
|
||||||
import { menu } from '$lib/components/Org/store';
|
import { menu } from '$lib/components/Org/store';
|
||||||
|
import { currentOrg } from '$lib/utils/store/org';
|
||||||
import { toggleBodyByMode } from '$lib/utils/functions/app';
|
import { toggleBodyByMode } from '$lib/utils/functions/app';
|
||||||
|
|
||||||
export let navClass = '';
|
export let navClass = '';
|
||||||
@@ -39,8 +42,27 @@
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</li>
|
</li>
|
||||||
<div class="">
|
<div class="">
|
||||||
<a href={$page.url.pathname} title="Go to ClassroomIO Home" id="logo" class="text-lg">
|
<a
|
||||||
ClassroomIO
|
href={$page.url.pathname}
|
||||||
|
title="Go to ClassroomIO Home"
|
||||||
|
id="logo"
|
||||||
|
class="text-lg flex items-center"
|
||||||
|
>
|
||||||
|
{#if $currentOrg.avatar_url}
|
||||||
|
<Avatar
|
||||||
|
src={$currentOrg.avatar_url}
|
||||||
|
name={$currentOrg.name}
|
||||||
|
shape="rounded-md"
|
||||||
|
width="w-7"
|
||||||
|
height="h-7"
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<TextChip value={$currentOrg.shortName} className="bg-primary-200 font-bold" />
|
||||||
|
{/if}
|
||||||
|
<span class="line-clamp-1">
|
||||||
|
{$currentOrg.name}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { InlineCalendar, Datepicker } from 'svelte-calendar';
|
|
||||||
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
|
||||||
|
|
||||||
import { formatUserUpcomingData, formatDate } from '$lib/utils/functions/routes/dashboard';
|
|
||||||
import { user } from '$lib/utils/store/user';
|
|
||||||
import { fetchUserUpcomingData } from '$lib/utils/services/dashboard';
|
|
||||||
import { isMobile } from '$lib/utils/store/useMobile';
|
|
||||||
import type { UserLessonDataType, LessonsByMonthIndexType } from '$lib/utils/types';
|
|
||||||
|
|
||||||
const theme = {
|
|
||||||
calendar: {
|
|
||||||
width: '400px',
|
|
||||||
shadow: '0px 0px 5px rgba(0, 0, 0, 0.25)'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let store;
|
|
||||||
let calendar: HTMLElement | null = null;
|
|
||||||
let lessonsByMonth: LessonsByMonthIndexType = {};
|
|
||||||
let prevSelectedDate: string = new Date().toUTCString();
|
|
||||||
let selectedDateLessonData: UserLessonDataType[] | [] = [];
|
|
||||||
|
|
||||||
const addDotsToCalendar = (currentMonthIndexInRenderedMonth: number) =>
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!calendar) calendar = document.getElementById('calendar');
|
|
||||||
// console.log(
|
|
||||||
// `currentMonthIndexInRenderedMonth`,
|
|
||||||
// currentMonthIndexInRenderedMonth
|
|
||||||
// );
|
|
||||||
|
|
||||||
const renderedMonths =
|
|
||||||
calendar?.querySelectorAll('.contents .container .stage .grid .grid') || [];
|
|
||||||
// console.log(`renderedMonths`, renderedMonths);
|
|
||||||
const currentMonthIndex = new Date($store?.selected).getMonth();
|
|
||||||
const currentMonth = renderedMonths[currentMonthIndexInRenderedMonth];
|
|
||||||
// console.log(`currentMonth`, currentMonth);
|
|
||||||
// All elements for each day in the month - with prev or next month
|
|
||||||
const currentMonthElements =
|
|
||||||
currentMonth?.querySelectorAll<HTMLElement>('a:not(.outsider)') || [];
|
|
||||||
// console.log(`currentMonthElements`, currentMonthElements, '\n\n');
|
|
||||||
const currentMonthData = lessonsByMonth[currentMonthIndex];
|
|
||||||
if (!currentMonthData) return;
|
|
||||||
|
|
||||||
for (let i in currentMonthElements) {
|
|
||||||
if (!currentMonthElements.hasOwnProperty(i)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dayElement = currentMonthElements[i];
|
|
||||||
dayElement.style.position = 'relative';
|
|
||||||
|
|
||||||
// The date is inside the element like <a>2</a>
|
|
||||||
const dateIndex = Number(dayElement.innerHTML);
|
|
||||||
if (!!currentMonthData[dateIndex]) {
|
|
||||||
// console.log(`dateIndex`, dateIndex);
|
|
||||||
// console.log(`lesson data`, currentMonthData[dateIndex]);
|
|
||||||
dayElement.insertAdjacentHTML('beforeend', '<span class="active-day">•</span>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
async function runFirstThings(currentSession: { id: string } | null) {
|
|
||||||
if (!currentSession || !currentSession.id) return;
|
|
||||||
|
|
||||||
const userUpcomingData = await fetchUserUpcomingData(currentSession.id);
|
|
||||||
|
|
||||||
lessonsByMonth = formatUserUpcomingData(userUpcomingData);
|
|
||||||
|
|
||||||
// Only for desktop. Only run on mobile when the user clicks on the date
|
|
||||||
if (!$isMobile) {
|
|
||||||
addDotsToCalendar(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDataOfSelectedDate(_selectedDate: string): UserLessonDataType[] | [] {
|
|
||||||
if (!_selectedDate) return [];
|
|
||||||
|
|
||||||
const selectedDate = new Date(_selectedDate).toUTCString();
|
|
||||||
|
|
||||||
const monthIndex = new Date(selectedDate).getMonth();
|
|
||||||
const dateIndex = new Date(selectedDate).getDate();
|
|
||||||
|
|
||||||
// If we changed the month, update the calendar dots
|
|
||||||
const prevMonthIndex = new Date(prevSelectedDate).getMonth();
|
|
||||||
if (prevMonthIndex !== monthIndex) {
|
|
||||||
const currentMonthIndexInRenderedMonth = monthIndex < prevMonthIndex ? 1 : 2;
|
|
||||||
|
|
||||||
addDotsToCalendar(currentMonthIndexInRenderedMonth);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSelectedDate = selectedDate;
|
|
||||||
|
|
||||||
const monthLessonData = lessonsByMonth[monthIndex];
|
|
||||||
if (!monthLessonData) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateLessonData = monthLessonData[dateIndex];
|
|
||||||
if (!dateLessonData) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return dateLessonData;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// Remove default shouldEnlargeDay=true when using <DatePicker /> on mobile
|
|
||||||
if ($store && $store.shouldEnlargeDay) {
|
|
||||||
$store.shouldEnlargeDay = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// This is here not called onMount because we want to call it as soon as we have the user's `currentSession` value
|
|
||||||
$: runFirstThings($user.currentSession);
|
|
||||||
|
|
||||||
// Every time the date changes
|
|
||||||
$: selectedDateLessonData = getDataOfSelectedDate($store?.selected);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Dashboard - ClassroomIO</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<section class="root w-full py-10 bg-gray-100 dark:bg-neutral-800">
|
|
||||||
<div class="calendar-root flex mx-2">
|
|
||||||
{#if !$isMobile}
|
|
||||||
<div id="calendar">
|
|
||||||
<InlineCalendar bind:store {theme} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
id={$isMobile ? 'calendar' : ''}
|
|
||||||
class="calendar-info flex-grow border border-gray-300 mx-2 rounded-md"
|
|
||||||
>
|
|
||||||
{#if $isMobile}
|
|
||||||
<div class="header w-full flex items-center">
|
|
||||||
<Datepicker bind:store let:key let:send let:receive>
|
|
||||||
<button
|
|
||||||
class="text-lg font-bold text-primary-700 p-3 hover:bg-gray-300 rounded-md"
|
|
||||||
in:receive|local={{ key }}
|
|
||||||
out:send|local={{ key }}
|
|
||||||
on:click={() => {
|
|
||||||
addDotsToCalendar(1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatDate($store?.selected)}
|
|
||||||
</button>
|
|
||||||
</Datepicker>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="header text-lg font-bold text-primary-700 flex px-5 items-center">
|
|
||||||
{formatDate($store?.selected)}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#each selectedDateLessonData as lessonData}
|
|
||||||
<div class="flex items-center justify-between p-5 lesson-data">
|
|
||||||
<div class="w-4/5">
|
|
||||||
<a
|
|
||||||
class="text-black-700 text-lg font-bold flex items-center"
|
|
||||||
href={`/courses/${lessonData.course_id}/lessons/${
|
|
||||||
lessonData.is_unlocked ? lessonData.lesson_id : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{lessonData.lesson_title}
|
|
||||||
{#if lessonData.is_unlocked}
|
|
||||||
<span class="ml-2 success">
|
|
||||||
<CheckmarkFilledIcon size={20} class="carbon-icon dark:text-white" />
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<span class="text-md ml-2">🔒</span>
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
<p class="dark:text-white text-grey text-sm flex items-center">
|
|
||||||
<a class="underline text-primary-700 my-2" href="/courses/{lessonData.course_id}">
|
|
||||||
{` ${lessonData.course_title}`}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{#if lessonData.is_unlocked}
|
|
||||||
<a
|
|
||||||
class="join-call rounded-3xl bg-primary-600 text-white py-3 font-bold shadow-lg {!lessonData.call_url &&
|
|
||||||
'opacity-50 pointer-events-none cursor-not-allowed'}"
|
|
||||||
href={!!lessonData.call_url ? lessonData.call_url : undefined}
|
|
||||||
target="_blank"
|
|
||||||
title={!!lessonData.call_url
|
|
||||||
? 'Click to join the call'
|
|
||||||
: 'No call link was added for this lesson. Ask your trainer'}
|
|
||||||
>
|
|
||||||
Join call
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p class="dark:text-white flex items-center justify-center w-full no-data">
|
|
||||||
No lesson on this day
|
|
||||||
</p>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
min-height: 93vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-root {
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-info {
|
|
||||||
max-width: 600px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
min-height: 61px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lesson-data {
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.join-call {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
max-height: 48px;
|
|
||||||
width: 110px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(span.active-day) {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
left: 40%;
|
|
||||||
color: var(--main-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
:global(span.active-day) {
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
+28
-28
@@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Schedule from '$lib/components/LMS/components/Schedule.svelte';
|
|
||||||
import Learning from '$lib/components/LMS/components/Learning.svelte';
|
import Learning from '$lib/components/LMS/components/Learning.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { profile } from '$lib/utils/store/user';
|
import { profile } from '$lib/utils/store/user';
|
||||||
import { fetchCourses } from '$lib/components/Courses/api';
|
|
||||||
import { currentOrg } from '$lib/utils/store/org';
|
import { currentOrg } from '$lib/utils/store/org';
|
||||||
|
import { fetchCourses } from '$lib/components/Courses/api';
|
||||||
import { courses, courseMetaDeta } from '$lib/components/Courses/store';
|
import { courses, courseMetaDeta } from '$lib/components/Courses/store';
|
||||||
import type { Course } from '$lib/utils/types';
|
import type { Course } from '$lib/utils/types';
|
||||||
|
|
||||||
@@ -70,38 +69,39 @@
|
|||||||
class="w-28 md:block md:w-1/3 lg:w-[275px] lg:h-[205px] mb-3 md:mb-0"
|
class="w-28 md:block md:w-1/3 lg:w-[275px] lg:h-[205px] mb-3 md:mb-0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<main class="flex w-full h-full flex-col xl:flex-row xl:gap-5">
|
<section class="flex w-full flex-col xl:flex-row xl:gap-5">
|
||||||
<div class="w-full h-full">
|
<div class="w-full md:w-[75%] mt-10 xl:mt-2">
|
||||||
<Schedule />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full h-full">
|
|
||||||
<Learning />
|
<Learning />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="mt-10 xl:mt-2 w-full md:w-[75%] xl:w-[400px]">
|
||||||
class="flex items-center justify-center border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded w-full xl:w-[400px] h-fit xl:h-[516px] overflow-y-auto p-3 xl:mt-11"
|
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Your Progress</p>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="w-full h-full flex flex-col sm:flex-row xl:flex-col items-center justify-between md:items-center xl:items-start gap-5 md:justify-around"
|
class="flex items-center justify-center border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded h-fit lg:h-[40vh] lg:overflow-y-auto p-3"
|
||||||
>
|
>
|
||||||
<div>
|
<div
|
||||||
<img src="/images/target.svg" alt="student Learning score" />
|
class="w-full h-full flex flex-col sm:flex-row xl:flex-col items-center justify-between md:items-center xl:items-start gap-5 md:justify-around"
|
||||||
</div>
|
>
|
||||||
<span class="text-center xl:text-start">
|
<div>
|
||||||
<p class="text-base font-semibold py-2 text-[#040F2D] dark:text-white">Your Progress</p>
|
<img src="/images/target.svg" alt="student Learning score" />
|
||||||
{#if totalLessons > 0}
|
</div>
|
||||||
<p class="text-xs font-normal text-[#656565] dark:text-white">
|
<span class="text-center xl:text-start">
|
||||||
{totalComPleted}/{totalLessons} lessons completed
|
<p class="text-base font-semibold py-2 text-[#040F2D] dark:text-white">
|
||||||
|
Your Progress
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{#if totalLessons > 0}
|
||||||
<p class="text-xs font-normal text-[#656565] dark:text-white">No courses started</p>
|
<p class="text-xs font-normal text-[#656565] dark:text-white">
|
||||||
{/if}
|
{totalComPleted}/{totalLessons} lessons completed
|
||||||
</span>
|
</p>
|
||||||
<h1 class="text-5xl md:text-6xl font-bold text-[#262626] dark:text-white my-0">
|
{:else}
|
||||||
{progressPercentage} %
|
<p class="text-xs font-normal text-[#656565] dark:text-white">No courses started</p>
|
||||||
</h1>
|
{/if}
|
||||||
|
</span>
|
||||||
|
<h1 class="text-5xl md:text-6xl font-bold text-[#262626] dark:text-white my-0">
|
||||||
|
{progressPercentage} %
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -114,84 +114,89 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="max-w-6xl mx-auto">
|
<section class="w-full max-w-6xl mx-auto">
|
||||||
<div class="m-5">
|
<div class="py-10 px-5">
|
||||||
<h1 class="text-3xl font-semibold my-4">Exercises</h1>
|
<div class="flex items-center justify-between mb-10">
|
||||||
<div class="flex items-center w-full">
|
<h1 class="dark:text-white text-3xl font-bold">Exercises</h1>
|
||||||
{#each sections as { id, title, items, className }, idx (id)}
|
</div>
|
||||||
<div
|
|
||||||
class="min-w-[355px] max-w-[355px] h-[516px] rounded-md bg-gray-100 dark:bg-black border border-gray-50 p-3 mr-3 overflow-hidden"
|
<div>
|
||||||
animate:flip={{ duration: flipDurationMs }}
|
<div class="flex items-center w-full">
|
||||||
>
|
{#each sections as { id, title, items, className }, idx (id)}
|
||||||
<div class="flex items-center mb-2 gap-2">
|
|
||||||
<p class="dark:text-white ml-2 font-bold">{title}</p>
|
|
||||||
<Chip value={items.length} {className} />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="content pr-2 overflow-y-auto mb-3"
|
class="min-w-[355px] max-w-[355px] h-[516px] rounded-md bg-gray-100 dark:bg-black border border-gray-50 p-3 mr-3 overflow-hidden"
|
||||||
use:dndzone={{
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
items,
|
|
||||||
flipDurationMs,
|
|
||||||
dropTargetStyle: { outline: 'blue' }
|
|
||||||
}}
|
|
||||||
on:consider={handleDndConsiderCards(idx)}
|
|
||||||
on:finalize={handleDndFinalizeCards(idx)}
|
|
||||||
>
|
>
|
||||||
{#if items.length === 0}
|
<div class="flex items-center mb-2 gap-2">
|
||||||
<div
|
<p class="dark:text-white ml-2 font-bold">{title}</p>
|
||||||
class="h-[100px]"
|
<Chip value={items.length} {className} />
|
||||||
use:dndzone={{
|
</div>
|
||||||
items: [], // Empty array for placeholder
|
<div
|
||||||
flipDurationMs,
|
class="content pr-2 overflow-y-auto mb-3"
|
||||||
dropTargetStyle: { outline: 'blue' }
|
use:dndzone={{
|
||||||
}}
|
items,
|
||||||
on:consider={handleDndConsiderCards(idx)}
|
flipDurationMs,
|
||||||
on:finalize={handleDndFinalizeCards(idx)}
|
dropTargetStyle: { outline: 'blue' }
|
||||||
>
|
}}
|
||||||
<!-- Placeholder content -->
|
on:consider={handleDndConsiderCards(idx)}
|
||||||
<p class="text-gray-500 dark:text-gray-400 text-left p-2">
|
on:finalize={handleDndFinalizeCards(idx)}
|
||||||
drop your {title} exercises here
|
>
|
||||||
</p>
|
{#if items.length === 0}
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
{#each items as item (item.id)}
|
|
||||||
<div
|
<div
|
||||||
class=" w-full my-2 mx-0 rounded-md bg-white dark:bg-gray-600 py-3 px-3"
|
class="h-[100px]"
|
||||||
animate:flip={{ duration: flipDurationMs }}
|
use:dndzone={{
|
||||||
|
items: [], // Empty array for placeholder
|
||||||
|
flipDurationMs,
|
||||||
|
dropTargetStyle: { outline: 'blue' }
|
||||||
|
}}
|
||||||
|
on:consider={handleDndConsiderCards(idx)}
|
||||||
|
on:finalize={handleDndFinalizeCards(idx)}
|
||||||
>
|
>
|
||||||
<a
|
<!-- Placeholder content -->
|
||||||
class="flex w-full items-center cursor-pointer text-black mb-2"
|
<p class="text-gray-500 dark:text-gray-400 text-left p-2">
|
||||||
href={`${$page.url.pathname}?submissionId=${item.id}`}
|
drop your {title} exercises here
|
||||||
>
|
|
||||||
<p class="dark:text-white ml-2 text-sm">
|
|
||||||
{item.student.username}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="text-primary-700 dark:text-white text-md font-bold"
|
|
||||||
href="{$page.url.pathname}?submissionId={item.id}"
|
|
||||||
>
|
|
||||||
{item.exercise.title}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="flex items-center no-underline hover:underline text-black my-2"
|
|
||||||
href="{$page.url?.pathname?.replace('submissions', 'lessons')}/{item.lesson
|
|
||||||
.id}/exercises/{item.exercise.id}"
|
|
||||||
>
|
|
||||||
<p class="dark:text-white text-grey text-sm">
|
|
||||||
#{item.lesson.title}
|
|
||||||
{`${item.tutor ? 'created by' + item.tutor.name : ''}`}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
<p class="dark:text-white text-gray-500 text-xs">
|
|
||||||
{item.submittedAt}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{:else}
|
||||||
{/if}
|
{#each items as item (item.id)}
|
||||||
|
<div
|
||||||
|
class=" w-full my-2 mx-0 rounded-md bg-white dark:bg-gray-600 py-3 px-3"
|
||||||
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="flex w-full items-center cursor-pointer text-black mb-2"
|
||||||
|
href={`${$page.url.pathname}?submissionId=${item.id}`}
|
||||||
|
>
|
||||||
|
<p class="dark:text-white ml-2 text-sm">
|
||||||
|
{item.student.username}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="text-primary-700 dark:text-white text-md font-bold"
|
||||||
|
href="{$page.url.pathname}?submissionId={item.id}"
|
||||||
|
>
|
||||||
|
{item.exercise.title}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="flex items-center no-underline hover:underline text-black my-2"
|
||||||
|
href="{$page.url?.pathname?.replace('submissions', 'lessons')}/{item.lesson
|
||||||
|
.id}/exercises/{item.exercise.id}"
|
||||||
|
>
|
||||||
|
<p class="dark:text-white text-grey text-sm">
|
||||||
|
#{item.lesson.title}
|
||||||
|
{`${item.tutor ? 'created by' + item.tutor.name : ''}`}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
<p class="dark:text-white text-gray-500 text-xs">
|
||||||
|
{item.submittedAt}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
import Tabs from '$lib/components/Tabs/index.svelte';
|
import Tabs from '$lib/components/Tabs/index.svelte';
|
||||||
import TabContent from '$lib/components/TabContent/index.svelte';
|
import TabContent from '$lib/components/TabContent/index.svelte';
|
||||||
import Courses from '$lib/components/Courses/index.svelte';
|
import Courses from '$lib/components/Courses/index.svelte';
|
||||||
import { courses } from '$lib/components/Courses/store';
|
import { fetchCourses } from '$lib/components/Courses/api';
|
||||||
|
import { profile } from '$lib/utils/store/user';
|
||||||
|
import { currentOrg } from '$lib/utils/store/org';
|
||||||
|
import { courses, courseMetaDeta, coursesComplete } from '$lib/components/Courses/store';
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
@@ -16,11 +19,29 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
let currentTab = tabs[0].value;
|
let currentTab = tabs[0].value;
|
||||||
|
let hasFetched = false;
|
||||||
|
|
||||||
const onChange =
|
function onChange(tab: 0) {
|
||||||
(tab = 0) =>
|
return () => (currentTab = tab);
|
||||||
() =>
|
}
|
||||||
(currentTab = tab);
|
|
||||||
|
async function getCourses(userId: string | null, orgId: string) {
|
||||||
|
if (hasFetched || !userId || !orgId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$courseMetaDeta.isLoading = true;
|
||||||
|
|
||||||
|
const coursesResult = await fetchCourses(userId, orgId);
|
||||||
|
console.log(`coursesResult`, coursesResult);
|
||||||
|
|
||||||
|
$courseMetaDeta.isLoading = false;
|
||||||
|
if (!coursesResult) return;
|
||||||
|
|
||||||
|
courses.set(coursesResult.allCourses);
|
||||||
|
hasFetched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: getCourses($profile.id, $currentOrg.id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="max-w-6xl mx-auto">
|
<section class="max-w-6xl mx-auto">
|
||||||
@@ -32,10 +53,18 @@
|
|||||||
<Tabs {tabs} {currentTab} {onChange}>
|
<Tabs {tabs} {currentTab} {onChange}>
|
||||||
<slot:fragment slot="content">
|
<slot:fragment slot="content">
|
||||||
<TabContent value={tabs[0].value} index={currentTab}>
|
<TabContent value={tabs[0].value} index={currentTab}>
|
||||||
<Courses courses={$courses} />
|
<Courses
|
||||||
|
courses={$courses}
|
||||||
|
emptyTitle="No Course In progress"
|
||||||
|
emptyDescription="Any course that you start will be displayed here"
|
||||||
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={tabs[1].value} index={currentTab}>
|
<TabContent value={tabs[1].value} index={currentTab}>
|
||||||
<Courses courses={[]} />
|
<Courses
|
||||||
|
courses={$coursesComplete}
|
||||||
|
emptyTitle="No Course Completed"
|
||||||
|
emptyDescription="Any course that you complete will be displayed here"
|
||||||
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</slot:fragment>
|
</slot:fragment>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -22,20 +22,26 @@
|
|||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="max-w-6xl mx-auto">
|
<section class="w-full max-w-6xl mx-auto">
|
||||||
<div class="m-2 md:m-5">
|
<div class="py-10 px-5">
|
||||||
<Tabs autoWidth bind:selected>
|
<div class="flex items-center justify-between mb-10">
|
||||||
{#each tabs as tab}
|
<h1 class="dark:text-white text-3xl font-bold">Settings</h1>
|
||||||
<Tab label={tab.label} href={tab.href} />
|
</div>
|
||||||
{/each}
|
|
||||||
<svelte:fragment slot="content">
|
<div class="">
|
||||||
<TabContent>
|
<Tabs autoWidth bind:selected>
|
||||||
<Profile />
|
{#each tabs as tab}
|
||||||
</TabContent>
|
<Tab label={tab.label} href={tab.href} />
|
||||||
<TabContent>
|
{/each}
|
||||||
<Integrations />
|
<svelte:fragment slot="content">
|
||||||
</TabContent>
|
<TabContent>
|
||||||
</svelte:fragment>
|
<Profile />
|
||||||
</Tabs>
|
</TabContent>
|
||||||
|
<TabContent>
|
||||||
|
<Integrations />
|
||||||
|
</TabContent>
|
||||||
|
</svelte:fragment>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -177,8 +177,6 @@
|
|||||||
return dateLessonData;
|
return dateLessonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToSite() {}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Remove default shouldEnlargeDay=true when <DatePicker /> on mobile
|
// Remove default shouldEnlargeDay=true when <DatePicker /> on mobile
|
||||||
if ($store && $store.shouldEnlargeDay) {
|
if ($store && $store.shouldEnlargeDay) {
|
||||||
|
|||||||
Reference in New Issue
Block a user