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;
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, {
title: course_title,
description: course_description,
logo: image,
is_published,
metadata: {
...(isObject($course.metadata) ? $course.metadata : {}),
lessonTabsOrder: tabs,
@@ -158,6 +167,7 @@
$course.title = course_title;
$course.description = course_description;
$course.logo = image;
$course.is_published = is_published;
$course.metadata = {
...(isObject($course.metadata) ? $course.metadata : {}),
lessonTabsOrder: tabs,
@@ -173,6 +183,7 @@
};
function setDefault(course) {
console.log('course.is_published', course.is_published);
if (course && Object.keys(course).length) {
$settings = {
course_title: course.title,
@@ -180,7 +191,8 @@
image: course.logo,
tabs: course.metadata.lessonTabsOrder || $settings.tabs,
grading: course.metadata.grading,
lesson_download: course.metadata.lessonDownload
lesson_download: course.metadata.lessonDownload,
is_published: course.is_published
};
}
}
@@ -305,6 +317,21 @@
/>
</Column>
</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">
<Column sm={8} md={8} lg={8}>
<SectionTitle>Delete course</SectionTitle>
@@ -10,5 +10,6 @@ export const settings = writable({
{ id: 2, name: 'Slide' },
{ 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 type { Writable } from 'svelte/store';
import { lessons } from './components/Lesson/store/lessons';
//@ts-ignore
import { ROLE } from '$lib/utils/constants/roles';
import type { Course } from '$lib/utils/types';
@@ -14,6 +13,9 @@ export const course: Writable<Course> = writable({
status: 'ACTIVE',
is_certificate_downloadable: false,
certificate_theme: 'professional',
is_published: false,
created_at: new Date().toDateString(),
updated_at: new Date().toDateString(),
metadata: {
requirements: '',
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
const { data: allCourses } = await supabase
.rpc('get_courses', {
org_id_arg: orgId
org_id_arg: orgId,
profile_id_arg: profileId
})
.eq('profile_id', profileId);
@@ -1,50 +1,25 @@
<script lang="ts">
import { Tag, ImageLoader, SkeletonPlaceholder } from 'carbon-components-svelte';
// import { ROLE } from '$lib/utils/constants/roles';
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 id = '';
export let slug = '';
export let title = '';
export let description = '';
// export let role_id = ROLE.STUDENT;
export let isPublished = false;
export let cost = 0;
export let totalLessons = 0;
export let currency = 'NGN';
export let isOnLandingPage = false;
// export let onDelete = (id: string) => {};
export let isLMS = false;
export let progressRate = 45;
let formatter = getCurrencyFormatter(currency);
// function getOptions() {
// return [
// {
// 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());
// }
$: console.log(progressRate);
$: formatter = getCurrencyFormatter(currency);
</script>
<a
@@ -72,21 +47,43 @@
</p>
</div>
<div class="px-4 border border-gray border-b-0 border-l-0 border-r-0 footer">
<p class="text-xs pt-2 pl-2 dark:text-white">{totalLessons} lessons</p>
<p class="text-xs py-2">
{#if isOnLandingPage}
<span class="px-2">{!cost ? 'Free' : formatter.format(cost)}</span>
{:else}
<Tag type={isPublished ? 'green' : 'cool-gray'}>
{#if isPublished}
Published
{:else}
Unpublished
{/if}
</Tag>
{/if}
</p>
<div
class="px-4 border border-gray border-b-0 border-l-0 border-r-0 flex justify-between items-center"
>
<div>
<p class="text-xs pt-2 {!isLMS && 'pl-2'} dark:text-white">{totalLessons} lessons</p>
<p class="text-xs py-2">
{#if isOnLandingPage}
<span class="px-2">{!cost ? 'Free' : formatter.format(cost)}</span>
{:else if isLMS}
<div class="flex items-center gap-2">
<div class=" relative bg-[#EAEAEA] w-[50px] h-1">
<div
style="width:{progressRate}%"
class={`absolute top-0 left-0 bg-primary-700 h-full`}
/>
</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>
</a>
@@ -97,7 +94,7 @@
}
.title,
.description {
height: 40px;
height: 42px;
line-height: 20px;
display: -webkit-box;
-webkit-box-orient: vertical;
+13 -2
View File
@@ -3,14 +3,23 @@
import Card from './components/Card/index.svelte';
import CardLoader from './components/Card/Loader.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 type { Course } from '$lib/utils/types';
import { appStore } from '$lib/utils/store/app';
export let courses: Course[] = [];
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
if (!progressRate || !totalLessons) {
return 0;
}
return Math.round((progressRate / totalLessons) * 100);
}
</script>
<CopyCourseModal />
<!-- <CopyCourseModal /> -->
<div class={`w-full ${$courseMetaDeta.isLoading || courses ? 'cards-container' : ''} my-4 mx-auto`}>
{#if $courseMetaDeta.isLoading}
@@ -30,6 +39,8 @@
cost={courseData.cost}
currency={courseData.currency}
totalLessons={courseData.total_lessons}
isLMS={$appStore.isOrgSite}
progressRate={calcProgressRate(courseData.progress_rate, courseData.total_lessons)}
/>
{/key}
{/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>
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, courseInProgress } from './store';
const gotoCourse = (title) => {
goto(`/courses/${title}`);
};
import { courseInProgress } from './store';
import Card from '$lib/components/Courses/components/Card/index.svelte';
</script>
<section>
@@ -17,44 +11,16 @@
class="flex items-center justify-center md:justify-start flex-wrap gap-4 dark:text-white mt-2"
>
{#each courseInProgress 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>
{#key data.id}
<Card
id={data.id}
bannerImage={data.src || '/images/classroomio-course-img-template.jpg'}
title={data.title}
description={data.description}
isLMS={true}
progressRate={data.progressRate}
/>
{/key}
{/each}
</div>
{:else}
@@ -1,5 +1,6 @@
export const mockData = [
{
id: '1',
title: 'Photo editing 101',
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 ...',
@@ -12,6 +13,7 @@ export const mockData = [
progressRate: 45
},
{
id: '2',
title: 'Colorful digital painting',
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 ...',
@@ -24,6 +26,7 @@ export const mockData = [
progressRate: 45
},
{
id: '3',
title: 'AI for dummys',
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 ...',
+3 -11
View File
@@ -8,7 +8,6 @@
import Close from 'carbon-icons-svelte/lib/Close.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { course } from '$lib/components/Course/store';
import { appStore } from '$lib/utils/store/app';
import { currentOrgPath } from '$lib/utils/store/org';
import { toggleBodyByMode } from '$lib/utils/functions/app';
@@ -33,13 +32,7 @@
}
}
function getPathName(_isCoursePage = false, slug = '') {
if (!_isCoursePage) {
return '/';
}
return slug ? `/course/${slug}` : `${$currentOrgPath}/courses`;
}
$: coursesPath = $appStore.isOrgSite ? '/lms/mylearning' : `${$currentOrgPath}/courses`;
</script>
<nav
@@ -61,7 +54,7 @@
<div class="hidden md:block">
<IconButton
onClick={() => {
goto(`${$currentOrgPath}/courses`);
goto(coursesPath);
}}
size="small"
>
@@ -70,8 +63,7 @@
</div>
{/if}
<a
href={getPathName(isCoursePage, $course.slug)}
target={isCoursePage && $course.slug ? '_blank' : ''}
href={coursesPath}
title="Go to {isCoursePage ? 'Courses' : 'ClassroomIO Home'}"
id="logo"
class="text-lg {isCoursePage && '-ml-2 md:ml-2'} line-clamp-1"
+6 -3
View File
@@ -21,9 +21,9 @@ interface CourseMetadata {
imgUrl: string;
};
reviews?: Array<Review>;
lessonTabsOrder: Array<Tabs>;
grading: boolean;
lessonDownload: boolean;
lessonTabsOrder?: Array<Tabs>;
grading?: boolean;
lessonDownload?: boolean;
}
// Generated from https://supabase-schema.vercel.app/
@@ -106,6 +106,9 @@ export interface Course {
is_certificate_downloadable?: boolean;
certificate_theme?: string;
status: string;
is_published?: boolean;
progress_rate?: number;
total_lessons?: number;
}
export interface Groupmember {
-1
View File
@@ -7,7 +7,6 @@
progressPercentage
} from '$lib/components/LMS/components/store';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import Box from '$lib/components/Box/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { goto } from '$app/navigation';
</script>
+29 -17
View File
@@ -1,34 +1,49 @@
<script>
import TextField from '$lib/components/Form/TextField.svelte';
<script lang="ts">
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 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 TabContent from '$lib/components/TabContent/index.svelte';
// let tabs = CONSTANTS.tabs;
import Courses from '$lib/components/Courses/index.svelte';
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 = [
{
label: 'All',
label: 'In progress',
value: 1
},
{
label: 'In progress',
value: 2
},
{
label: 'Complete',
value: 3
value: 2
}
];
let currentTab = tabs[0].value;
let hasFetched = false;
const onChange =
(tab = 0) =>
() =>
(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>
<section class="max-w-6xl mx-auto">
@@ -40,12 +55,9 @@
<Tabs {tabs} {currentTab} {onChange}>
<slot:fragment slot="content">
<TabContent value={tabs[0].value} index={currentTab}>
<AllLearning />
<Courses courses={$courses} />
</TabContent>
<TabContent value={tabs[1].value} index={currentTab}>
<InProgress />
</TabContent>
<TabContent value={tabs[2].value} index={currentTab}>
<Completed />
</TabContent>
</slot:fragment>