CLASSROOMIO-T-108: Connect my learning page to BE

This commit is contained in:
Rotimi Best
2023-09-28 11:45:50 +01:00
parent 1b708211c7
commit acfd01316f
13 changed files with 147 additions and 202 deletions
@@ -142,11 +142,20 @@
} }
isSaving = true; isSaving = true;
try { try {
const { course_title, course_description, image, tabs, grading, lesson_download } = $settings; const {
course_title,
course_description,
image,
tabs,
grading,
lesson_download,
is_published
} = $settings;
await updateCourse($course.id, avatar, { await updateCourse($course.id, avatar, {
title: course_title, title: course_title,
description: course_description, description: course_description,
logo: image, logo: image,
is_published,
metadata: { metadata: {
...(isObject($course.metadata) ? $course.metadata : {}), ...(isObject($course.metadata) ? $course.metadata : {}),
lessonTabsOrder: tabs, lessonTabsOrder: tabs,
@@ -158,6 +167,7 @@
$course.title = course_title; $course.title = course_title;
$course.description = course_description; $course.description = course_description;
$course.logo = image; $course.logo = image;
$course.is_published = is_published;
$course.metadata = { $course.metadata = {
...(isObject($course.metadata) ? $course.metadata : {}), ...(isObject($course.metadata) ? $course.metadata : {}),
lessonTabsOrder: tabs, lessonTabsOrder: tabs,
@@ -173,6 +183,7 @@
}; };
function setDefault(course) { function setDefault(course) {
console.log('course.is_published', course.is_published);
if (course && Object.keys(course).length) { if (course && Object.keys(course).length) {
$settings = { $settings = {
course_title: course.title, course_title: course.title,
@@ -180,7 +191,8 @@
image: course.logo, image: course.logo,
tabs: course.metadata.lessonTabsOrder || $settings.tabs, tabs: course.metadata.lessonTabsOrder || $settings.tabs,
grading: course.metadata.grading, grading: course.metadata.grading,
lesson_download: course.metadata.lessonDownload lesson_download: course.metadata.lessonDownload,
is_published: course.is_published
}; };
} }
} }
@@ -305,6 +317,21 @@
/> />
</Column> </Column>
</Row> </Row>
<!-- Publish Course -->
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={8} md={8} lg={8}>
<SectionTitle>Publish Course</SectionTitle>
<p>This determines if your course displays on your landing page</p>
</Column>
<Column sm={8} md={8} lg={8}>
<Toggle size="sm" bind:toggled={$settings.is_published}>
<span slot="labelA" style="color: gray">Unpublished</span>
<span slot="labelB" style="color: gray">Published</span>
</Toggle>
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c"> <Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={8} md={8} lg={8}> <Column sm={8} md={8} lg={8}>
<SectionTitle>Delete course</SectionTitle> <SectionTitle>Delete course</SectionTitle>
@@ -10,5 +10,6 @@ export const settings = writable({
{ id: 2, name: 'Slide' }, { id: 2, name: 'Slide' },
{ id: 3, name: 'Video' } { id: 3, name: 'Video' }
], ],
lesson_download: false lesson_download: false,
is_published: false
}); });
+3 -1
View File
@@ -1,7 +1,6 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import { lessons } from './components/Lesson/store/lessons'; import { lessons } from './components/Lesson/store/lessons';
//@ts-ignore
import { ROLE } from '$lib/utils/constants/roles'; import { ROLE } from '$lib/utils/constants/roles';
import type { Course } from '$lib/utils/types'; import type { Course } from '$lib/utils/types';
@@ -14,6 +13,9 @@ export const course: Writable<Course> = writable({
status: 'ACTIVE', status: 'ACTIVE',
is_certificate_downloadable: false, is_certificate_downloadable: false,
certificate_theme: 'professional', certificate_theme: 'professional',
is_published: false,
created_at: new Date().toDateString(),
updated_at: new Date().toDateString(),
metadata: { metadata: {
requirements: '', requirements: '',
description: '', description: '',
+2 -1
View File
@@ -6,7 +6,8 @@ export async function fetchCourses(profileId, orgId) {
// Gets courses for a particular organisation where the current logged in user is a groupmember // Gets courses for a particular organisation where the current logged in user is a groupmember
const { data: allCourses } = await supabase const { data: allCourses } = await supabase
.rpc('get_courses', { .rpc('get_courses', {
org_id_arg: orgId org_id_arg: orgId,
profile_id_arg: profileId
}) })
.eq('profile_id', profileId); .eq('profile_id', profileId);
@@ -1,50 +1,25 @@
<script lang="ts"> <script lang="ts">
import { Tag, ImageLoader, SkeletonPlaceholder } from 'carbon-components-svelte'; import { Tag, ImageLoader, SkeletonPlaceholder } from 'carbon-components-svelte';
// import { ROLE } from '$lib/utils/constants/roles';
import getCurrencyFormatter from '$lib/utils/functions/getCurrencyFormatter'; import getCurrencyFormatter from '$lib/utils/functions/getCurrencyFormatter';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
export let bannerImage: string | undefined; export let bannerImage: string | undefined;
export let id = ''; export let id = '';
export let slug = ''; export let slug = '';
export let title = ''; export let title = '';
export let description = ''; export let description = '';
// export let role_id = ROLE.STUDENT;
export let isPublished = false; export let isPublished = false;
export let cost = 0; export let cost = 0;
export let totalLessons = 0; export let totalLessons = 0;
export let currency = 'NGN'; export let currency = 'NGN';
export let isOnLandingPage = false; export let isOnLandingPage = false;
// export let onDelete = (id: string) => {}; export let isLMS = false;
export let progressRate = 45;
let formatter = getCurrencyFormatter(currency); $: console.log(progressRate);
// function getOptions() {
// return [ $: formatter = getCurrencyFormatter(currency);
// {
// label: 'Make a copy',
// // Show this option only if user has admin control over this course.
// show: () => role_id === ROLE.ADMIN || role_id === ROLE.TUTOR,
// onClick() {
// // IIFI of copyCourseModal
// (() => {
// $copyCourseModal.id = id;
// $copyCourseModal.open = true;
// $copyCourseModal.title = `Copy of ${title}`;
// })();
// }
// }
// // {
// // label: 'Delete',
// // show: () => role_id === ROLE.TUTOR,
// // onClick() {
// // (async () => {
// // await deleteCourse(id);
// // onDelete(id);
// // console.log('Deleting');
// // })();
// // }
// // }
// ].filter((option) => option.show());
// }
</script> </script>
<a <a
@@ -72,21 +47,43 @@
</p> </p>
</div> </div>
<div class="px-4 border border-gray border-b-0 border-l-0 border-r-0 footer"> <div
<p class="text-xs pt-2 pl-2 dark:text-white">{totalLessons} lessons</p> class="px-4 border border-gray border-b-0 border-l-0 border-r-0 flex justify-between items-center"
<p class="text-xs py-2"> >
{#if isOnLandingPage} <div>
<span class="px-2">{!cost ? 'Free' : formatter.format(cost)}</span> <p class="text-xs pt-2 {!isLMS && 'pl-2'} dark:text-white">{totalLessons} lessons</p>
{:else} <p class="text-xs py-2">
<Tag type={isPublished ? 'green' : 'cool-gray'}> {#if isOnLandingPage}
{#if isPublished} <span class="px-2">{!cost ? 'Free' : formatter.format(cost)}</span>
Published {:else if isLMS}
{:else} <div class="flex items-center gap-2">
Unpublished <div class=" relative bg-[#EAEAEA] w-[50px] h-1">
{/if} <div
</Tag> style="width:{progressRate}%"
{/if} class={`absolute top-0 left-0 bg-primary-700 h-full`}
</p> />
</div>
<p class="text-xs text-[#656565] dark:text-white">{progressRate}%</p>
</div>
{:else}
<Tag type={isPublished ? 'green' : 'cool-gray'}>
{#if isPublished}
Published
{:else}
Unpublished
{/if}
</Tag>
{/if}
</p>
</div>
{#if isLMS}
<PrimaryButton
label={'Continue Course'}
variant={VARIANTS.OUTLINED}
className="rounded-none text-primary-600"
/>
{/if}
</div> </div>
</a> </a>
@@ -97,7 +94,7 @@
} }
.title, .title,
.description { .description {
height: 40px; height: 42px;
line-height: 20px; line-height: 20px;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
+13 -2
View File
@@ -3,14 +3,23 @@
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 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';
export let courses: Course[] = []; export let courses: Course[] = [];
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
if (!progressRate || !totalLessons) {
return 0;
}
return Math.round((progressRate / totalLessons) * 100);
}
</script> </script>
<CopyCourseModal /> <!-- <CopyCourseModal /> -->
<div class={`w-full ${$courseMetaDeta.isLoading || courses ? 'cards-container' : ''} my-4 mx-auto`}> <div class={`w-full ${$courseMetaDeta.isLoading || courses ? 'cards-container' : ''} my-4 mx-auto`}>
{#if $courseMetaDeta.isLoading} {#if $courseMetaDeta.isLoading}
@@ -30,6 +39,8 @@
cost={courseData.cost} cost={courseData.cost}
currency={courseData.currency} currency={courseData.currency}
totalLessons={courseData.total_lessons} totalLessons={courseData.total_lessons}
isLMS={$appStore.isOrgSite}
progressRate={calcProgressRate(courseData.progress_rate, courseData.total_lessons)}
/> />
{/key} {/key}
{/each} {/each}
@@ -1,69 +0,0 @@
<script>
import { goto } from '$app/navigation';
import Box from '$lib/components/Box/index.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { mockData } from './store';
const gotoCourse = (title) => {
goto(`/courses/${title}`);
};
</script>
<section>
{#if mockData.length > 0}
<div
class="flex items-center justify-center md:justify-start flex-wrap gap-4 dark:text-white mt-2"
>
{#each mockData as data}
<div
class="w-80 min-h-[350px] flex items-start justify-start flex-wrap flex-col border rounded mr-2 mb-5 border-gray-200"
>
<img src={data.src} alt="course" class="w-80 h-[180px] object-cover" />
<div class="w-full p-3">
<p
class="line-clamp-2 text-xl lg:text-2xl font-normal text-[#262626] py-1 dark:text-white"
>
{data.title}
</p>
<p class="line-clamp-2 text-base text-[#656565] dark:text-white py-1">
{data.description}
</p>
<p class="py-1 text-[#7888B7] dark:text-white">By {data.author}</p>
<span class="flex flex-wrap flex-row items-start gap-2 justify-between mt-8">
<span>
<p>{data.lessons} lessons</p>
{#if data.inProgress}
<div class="flex items-center gap-2">
<div class=" relative bg-[#EAEAEA] w-full h-1">
<div
class={`absolute top-0 left-0 bg-primary-700 h-full ${data.progress} ]`}
/>
</div>
<p class="text-xs text-[#656565] dark:text-white">{data.progressRate}%</p>
</div>
{/if}
</span>
<PrimaryButton
label={data.inProgress ? 'Continue Course' : 'Start Course'}
variant={VARIANTS.OUTLINED}
className="rounded-none text-[#0233BD]"
onClick={() => gotoCourse(data.title)}
/>
</span>
</div>
</div>
{/each}
</div>
{:else}
<Box className="flex flex-col items-center mt-2">
<CoursesEmptyIcon />
<h3 class="text-center">You haven't started any course yet</h3>
<p class="w-[70%] md:w-[35%] text-center">
Once you start a course, your progress will reflect here
</p>
</Box>
{/if}
</section>
@@ -1,14 +1,8 @@
<script> <script>
import { goto } from '$app/navigation';
import Box from '$lib/components/Box/index.svelte'; import Box from '$lib/components/Box/index.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte'; import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants'; import { courseInProgress } from './store';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; import Card from '$lib/components/Courses/components/Card/index.svelte';
import { mockData, courseInProgress } from './store';
const gotoCourse = (title) => {
goto(`/courses/${title}`);
};
</script> </script>
<section> <section>
@@ -17,44 +11,16 @@
class="flex items-center justify-center md:justify-start flex-wrap gap-4 dark:text-white mt-2" class="flex items-center justify-center md:justify-start flex-wrap gap-4 dark:text-white mt-2"
> >
{#each courseInProgress as data} {#each courseInProgress as data}
<div {#key data.id}
class="w-80 min-h-[350px] flex items-start justify-start flex-wrap flex-col border rounded mr-2 mb-5 border-gray-200" <Card
> id={data.id}
<img src={data.src} alt="course" class="w-80 h-[180px] object-cover" /> bannerImage={data.src || '/images/classroomio-course-img-template.jpg'}
title={data.title}
<div class="w-full p-3"> description={data.description}
<p isLMS={true}
class="line-clamp-2 text-xl lg:text-2xl font-normal text-[#262626] py-1 dark:text-white" progressRate={data.progressRate}
> />
{data.title} {/key}
</p>
<p class="line-clamp-2 text-base text-[#656565] dark:text-white py-1">
{data.description}
</p>
<p class="py-1 text-[#7888B7] dark:text-white">By {data.author}</p>
<span class="flex flex-wrap flex-row items-start gap-2 justify-between mt-8">
<span>
<p>{data.lessons} lessons</p>
{#if data.inProgress}
<div class="flex items-center gap-2">
<div class=" relative bg-[#EAEAEA] w-full h-1">
<div
class={`absolute top-0 left-0 bg-primary-700 h-full ${data.progress} ]`}
/>
</div>
<p class="text-xs text-[#656565] dark:text-white">{data.progressRate}%</p>
</div>
{/if}
</span>
<PrimaryButton
label={data.inProgress ? 'Continue Course' : 'Start Course'}
variant={VARIANTS.OUTLINED}
className="rounded-none text-[#0233BD]"
onClick={() => gotoCourse(data.title)}
/>
</span>
</div>
</div>
{/each} {/each}
</div> </div>
{:else} {:else}
@@ -1,5 +1,6 @@
export const mockData = [ export const mockData = [
{ {
id: '1',
title: 'Photo editing 101', title: 'Photo editing 101',
description: description:
'Learn the different techniques to edit photos using Adobe Lightroom.Learn the basics of figma and start design web apps for the world ...Learn the basics of figma and start design web apps for the world ...', 'Learn the different techniques to edit photos using Adobe Lightroom.Learn the basics of figma and start design web apps for the world ...Learn the basics of figma and start design web apps for the world ...',
@@ -12,6 +13,7 @@ export const mockData = [
progressRate: 45 progressRate: 45
}, },
{ {
id: '2',
title: 'Colorful digital painting', title: 'Colorful digital painting',
description: description:
'Learn the different techniques to edit photos using Adobe Lightroom.Learn the basics of figma and start design web apps for the world ...Learn the basics of figma and start design web apps for the world ...', 'Learn the different techniques to edit photos using Adobe Lightroom.Learn the basics of figma and start design web apps for the world ...Learn the basics of figma and start design web apps for the world ...',
@@ -24,6 +26,7 @@ export const mockData = [
progressRate: 45 progressRate: 45
}, },
{ {
id: '3',
title: 'AI for dummys', title: 'AI for dummys',
description: description:
'Learn the different techniques to edit photos using Adobe Lightroom.Learn the basics of figma and start design web apps for the world ...Learn the basics of figma and start design web apps for the world ...', 'Learn the different techniques to edit photos using Adobe Lightroom.Learn the basics of figma and start design web apps for the world ...Learn the basics of figma and start design web apps for the world ...',
+3 -11
View File
@@ -8,7 +8,6 @@
import Close from 'carbon-icons-svelte/lib/Close.svelte'; import Close from 'carbon-icons-svelte/lib/Close.svelte';
import IconButton from '$lib/components/IconButton/index.svelte'; import IconButton from '$lib/components/IconButton/index.svelte';
import { course } from '$lib/components/Course/store';
import { appStore } from '$lib/utils/store/app'; import { appStore } from '$lib/utils/store/app';
import { currentOrgPath } from '$lib/utils/store/org'; import { currentOrgPath } from '$lib/utils/store/org';
import { toggleBodyByMode } from '$lib/utils/functions/app'; import { toggleBodyByMode } from '$lib/utils/functions/app';
@@ -33,13 +32,7 @@
} }
} }
function getPathName(_isCoursePage = false, slug = '') { $: coursesPath = $appStore.isOrgSite ? '/lms/mylearning' : `${$currentOrgPath}/courses`;
if (!_isCoursePage) {
return '/';
}
return slug ? `/course/${slug}` : `${$currentOrgPath}/courses`;
}
</script> </script>
<nav <nav
@@ -61,7 +54,7 @@
<div class="hidden md:block"> <div class="hidden md:block">
<IconButton <IconButton
onClick={() => { onClick={() => {
goto(`${$currentOrgPath}/courses`); goto(coursesPath);
}} }}
size="small" size="small"
> >
@@ -70,8 +63,7 @@
</div> </div>
{/if} {/if}
<a <a
href={getPathName(isCoursePage, $course.slug)} href={coursesPath}
target={isCoursePage && $course.slug ? '_blank' : ''}
title="Go to {isCoursePage ? 'Courses' : 'ClassroomIO Home'}" title="Go to {isCoursePage ? 'Courses' : 'ClassroomIO Home'}"
id="logo" id="logo"
class="text-lg {isCoursePage && '-ml-2 md:ml-2'} line-clamp-1" class="text-lg {isCoursePage && '-ml-2 md:ml-2'} line-clamp-1"
+6 -3
View File
@@ -21,9 +21,9 @@ interface CourseMetadata {
imgUrl: string; imgUrl: string;
}; };
reviews?: Array<Review>; reviews?: Array<Review>;
lessonTabsOrder: Array<Tabs>; lessonTabsOrder?: Array<Tabs>;
grading: boolean; grading?: boolean;
lessonDownload: boolean; lessonDownload?: boolean;
} }
// Generated from https://supabase-schema.vercel.app/ // Generated from https://supabase-schema.vercel.app/
@@ -106,6 +106,9 @@ export interface Course {
is_certificate_downloadable?: boolean; is_certificate_downloadable?: boolean;
certificate_theme?: string; certificate_theme?: string;
status: string; status: string;
is_published?: boolean;
progress_rate?: number;
total_lessons?: number;
} }
export interface Groupmember { export interface Groupmember {
-1
View File
@@ -7,7 +7,6 @@
progressPercentage progressPercentage
} from '$lib/components/LMS/components/store'; } from '$lib/components/LMS/components/store';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import Box from '$lib/components/Box/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';
</script> </script>
+29 -17
View File
@@ -1,34 +1,49 @@
<script> <script lang="ts">
import TextField from '$lib/components/Form/TextField.svelte';
import { Search } from 'carbon-components-svelte'; import { Search } from 'carbon-components-svelte';
import AllLearning from '$lib/components/LMS/components/AllLearning.svelte';
import Completed from '$lib/components/LMS/components/Completed.svelte'; import Completed from '$lib/components/LMS/components/Completed.svelte';
import InProgress from '$lib/components/LMS/components/InProgress.svelte'; import { courseInProgress } from '$lib/components/LMS/components/store';
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';
// let tabs = CONSTANTS.tabs; import { courses, courseMetaDeta } from '$lib/components/Courses/store';
import { profile } from '$lib/utils/store/user';
import { fetchCourses } from '$lib/components/Courses/api';
import { currentOrg } from '$lib/utils/store/org';
const tabs = [ const tabs = [
{ {
label: 'All', label: 'In progress',
value: 1 value: 1
}, },
{
label: 'In progress',
value: 2
},
{ {
label: 'Complete', label: 'Complete',
value: 3 value: 2
} }
]; ];
let currentTab = tabs[0].value; let currentTab = tabs[0].value;
let hasFetched = false;
const onChange = const onChange =
(tab = 0) => (tab = 0) =>
() => () =>
(currentTab = tab); (currentTab = tab);
async function getCourses(userId: string | null, orgId: string) {
if (!hasFetched) {
$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">
@@ -40,12 +55,9 @@
<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}>
<AllLearning /> <Courses courses={$courses} />
</TabContent> </TabContent>
<TabContent value={tabs[1].value} index={currentTab}> <TabContent value={tabs[1].value} index={currentTab}>
<InProgress />
</TabContent>
<TabContent value={tabs[2].value} index={currentTab}>
<Completed /> <Completed />
</TabContent> </TabContent>
</slot:fragment> </slot:fragment>