From 7296fdadab913d039fcf600114aebdaf2d5c0459 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Mon, 12 Aug 2024 16:05:22 +0100 Subject: [PATCH 01/22] feat: Pathways News Feed --- .../components/PathwayContainer.svelte | 80 ++++- .../src/lib/components/Pathways/functions.ts | 2 +- .../src/lib/components/Pathways/store.ts | 115 ++++++- .../src/lib/utils/services/pathways/index.ts | 85 +++++ apps/dashboard/src/lib/utils/types/index.ts | 13 +- .../src/routes/pathways/[id]/+page.svelte | 321 +++++++++++++++++- 6 files changed, 598 insertions(+), 18 deletions(-) diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte index 1de399200..fc0296346 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte @@ -1,14 +1,65 @@ @@ -21,10 +72,35 @@ {/if} + +
+

+ {$t('course.not_permitted.body')} +

+ +
+ { + goto('/org/*'); + }} + /> +
+
+
+
- + {#if isLandingPage} + + {/if} + + + {#if isPermitted} + + {/if}
diff --git a/apps/dashboard/src/lib/components/Pathways/functions.ts b/apps/dashboard/src/lib/components/Pathways/functions.ts index 9c138c7b1..d1aee2838 100644 --- a/apps/dashboard/src/lib/components/Pathways/functions.ts +++ b/apps/dashboard/src/lib/components/Pathways/functions.ts @@ -18,4 +18,4 @@ export function getPathwayNavItemRoute(pathwayId = '', routeId?: string) { } return `${path}/${routeId}`; -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/components/Pathways/store.ts b/apps/dashboard/src/lib/components/Pathways/store.ts index 5f30f08b5..67e3c8d41 100644 --- a/apps/dashboard/src/lib/components/Pathways/store.ts +++ b/apps/dashboard/src/lib/components/Pathways/store.ts @@ -1,5 +1,5 @@ import { writable } from 'svelte/store'; -import type { Pathway, PathwayCourse } from '$lib/utils/types'; +import type { Pathway, PathwayCourse, GroupStore } from '$lib/utils/types'; export const addCourseModal = writable({ open: false, @@ -8,10 +8,16 @@ export const addCourseModal = writable({ export const courses = writable([]); -export const pathway = writable({ +export const group = writable({ + id: '', + tutors: [], + students: [], + people: [] +}); + +export const defaultPathway: Pathway = { id: 'pathway-one', title: 'pathwayOne', - avatar: '', description: '', prerequisite: '', is_published: false, @@ -44,7 +50,10 @@ export const pathway = writable({ updated_at: '' } ], - is_published: false + is_published: false, + created_at: '', + + updated_at: '' }, { id: '73f9ascas2bda-f306-4c7b-88d3-d3a4ed37fb06', @@ -65,7 +74,10 @@ export const pathway = writable({ updated_at: '' } ], - is_published: false + is_published: false, + created_at: '', + + updated_at: '' }, { id: '41afdjmh56e-938c-45be-8f71-e59465dacce1', @@ -86,7 +98,10 @@ export const pathway = writable({ updated_at: '' } ], - is_published: false + is_published: false, + created_at: '', + + updated_at: '' }, { id: 'ef15evr6ee-018d-48ab-a195-8030366aae06', @@ -107,7 +122,10 @@ export const pathway = writable({ updated_at: '' } ], - is_published: true + is_published: true, + created_at: '', + + updated_at: '' }, { id: 'ef15egcg6ee-018d-48ab-a195-8030366aae06', @@ -128,8 +146,87 @@ export const pathway = writable({ updated_at: '' } ], - is_published: true + is_published: true, + created_at: '', + + updated_at: '' } ], selectedCourses: [] -}); +} + +export const pathway = writable({ ... defaultPathway }); + +export async function setPathway(data: Pathway, setCourse = true) { + if (!data || !(Object.values(data) && Object.values(data).length)) return; + // const tutorsById = {}; + + if (data.group) { + const groupData = Object.assign(data.group, { + tutors: [], + students: [], + people: [] + }) as GroupStore; + + if (Array.isArray(groupData.members)) { + for (const member of groupData.members) { + if (member.role_id === ROLE.STUDENT) { + groupData.students.push(member); + } else if (member.profile) { + groupData.tutors.push({ + ...member.profile, + memberId: member.id + }); + } + } + + groupData.people = groupData.members; + } + + delete groupData.members; + + group.set(groupData); + } + + if (setCourse) { + const orderedCourses = (data.courses || []).sort( + (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() + ); + // .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); + // .map((lesson) => ({ + // ...lesson, + // profile: lesson.profile && tutorsById[lesson.profile.id], + // })); + courses.set(orderedCourses); + } + + delete data.courses; + + if (data.metadata && !Object.values(data.metadata)) { + data.metadata = { + requirements: '', + description: '', + goals: '', + videoUrl: '', + showDiscount: false, + discount: 0, + reward: { + show: false, + description: '' + }, + instructor: { + name: '', + role: '', + coursesNo: 0, + description: '', + imgUrl: '' + }, + allowNewStudent: false + }; + } + + if (!data.certificate_theme) { + data.certificate_theme = 'professional'; + } + pathway.set(data); +} diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index cbf194bed..a138a7987 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -1,5 +1,90 @@ +import { STATUS } from '$lib/components/Course/components/Lesson/Exercise/constants'; import { supabase } from '$lib/utils/functions/supabase'; +import type { Pathway } from '$lib/utils/types'; +import type { PostgrestSingleResponse } from '@supabase/supabase-js'; export function addPathwayGroupMember(member: any) { return supabase.from('groupmember').insert(member).select(); } + + +const SLUG_QUERY = ` + id, + title, + type, + description, + overview, + logo, + is_published, + slug, + cost, + currency, + metadata, + is_certificate_downloadable, + certificate_theme, + courses:course( + id, title, + ) +`; + +const ID_QUERY = ` + id, + title, + type, + description, + overview, + logo, + is_published, + group(*, + members:groupmember(*, + profile(*) + ) + ), + slug, + cost, + currency, + metadata, + is_certificate_downloadable, + certificate_theme, + courses:course( + id, title,public, lesson_at, is_unlocked, order, created_at, + note, videos, slide_url, call_url, totalExercises:exercise(count), totalComments:lesson_comment(count), + profile:teacher_id(id, avatar_url, fullname), + lesson_completion(id, profile_id, is_complete) + ), + attendance:group_attendance(*), +`; + +export async function fetchPathway(courseId?: Pathway['id'], slug?: Pathway['slug']) { + const match: { slug?: string; id?: string; status?: string } = {}; + + if (slug) { + match.slug = slug; + } else { + match.id = courseId; + } + + match.status = STATUS[STATUS.ACTIVE]; + + const response: PostgrestSingleResponse = await supabase + .from('course') + .select(slug ? SLUG_QUERY : ID_QUERY) + .match(match) + .single(); + + const { data, error } = response; + + console.log(`error`, error); + console.log(`data`, data); + if (!data || error) { + console.log(`data`, data); + console.log(`fetchCourse => error`, error); + // return this.redirect(307, '/courses'); + return { data, error }; + } + + return { + data, + error + }; +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/types/index.ts b/apps/dashboard/src/lib/utils/types/index.ts index 4fe262cfb..4778a43de 100644 --- a/apps/dashboard/src/lib/utils/types/index.ts +++ b/apps/dashboard/src/lib/utils/types/index.ts @@ -34,6 +34,15 @@ export interface GroupPerson { fullname?: string; } +export interface GroupStore { + id: string; + tutors: GroupPerson[]; + students: GroupPerson[]; + people: GroupPerson[]; + members?: GroupPerson[]; + memberId?: string; +}; + export interface CustomQuestionType { id: number; label: any; @@ -294,6 +303,8 @@ export interface PathwayCourse { is_unlocked: boolean; is_completed: CourseCompletion[]; is_published: boolean; + created_at: string; + updated_at: string; } export interface Pathway { @@ -322,7 +333,7 @@ export interface Pathway { lms_certificate: boolean; courses_certificate: string; prerequisite: string; - courses: PathwayCourse[]; + courses?: PathwayCourse[]; selectedCourses: PathwayCourse[]; } diff --git a/apps/dashboard/src/routes/pathways/[id]/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/+page.svelte index 02c2933ee..5733a03af 100644 --- a/apps/dashboard/src/routes/pathways/[id]/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/+page.svelte @@ -1,9 +1,320 @@ - - -
-

News feed page o!

-
+ + { + goto(`/pathways/${data.pathwayId}/courses`); + }} + > + + + + ($isNewFeedModal.open = true)} + /> + + + + + + + { + $newsFeed = [newFeed, ...$newsFeed]; + }} + {onEdit} + /> + + {#if isLoading} +
+ + + +
+ {:else if !$newsFeed.length} + +
+ Lesson +

{$t('course.navItem.news_feed.body_header')}

+

+ {$t('course.navItem.news_feed.body_content')} +

+
+
+ {:else} + {#each $newsFeed as feed} + {#if feed.isPinned} +
+ + +

{$t('course.navItem.news_feed.pinned')}

+
+ {/if} + + {/each} + {/if} +
+
From 8d0d32e4ea10a6a19af842c1fc009a661b8aa9ed Mon Sep 17 00:00:00 2001 From: tunny17 Date: Wed, 14 Aug 2024 09:08:33 +0100 Subject: [PATCH 02/22] feat: completion of newsfeed integration --- .../NewsFeed/DeleteFeedConfirmation.svelte | 40 +++ .../components/NewsFeed/NewFeedModal.svelte | 143 +++++++++++ .../components/NewsFeed/NewsFeedCard.svelte | 241 ++++++++++++++++++ .../components/NewsFeed/NewsFeedLoader.svelte | 11 + .../Pathways/components/NewsFeed/store.ts | 8 + .../components/PathwayContainer.svelte | 10 +- .../src/lib/components/Pathways/store.ts | 2 +- .../src/lib/utils/services/pathways/index.ts | 42 ++- .../utils/services/pathways/newsfeed/index.ts | 185 ++++++++++++++ .../src/lib/utils/translations/de.json | 26 +- .../src/lib/utils/translations/en.json | 7 +- .../src/lib/utils/translations/es.json | 26 +- .../src/lib/utils/translations/fr.json | 26 +- .../src/lib/utils/translations/hi.json | 26 +- .../src/lib/utils/translations/pt.json | 26 +- .../src/lib/utils/translations/ru.json | 26 +- .../src/lib/utils/translations/vi.json | 26 +- apps/dashboard/src/lib/utils/types/org.ts | 3 + .../src/routes/pathways/[id]/+page.svelte | 36 +-- 19 files changed, 846 insertions(+), 64 deletions(-) create mode 100644 apps/dashboard/src/lib/components/Pathways/components/NewsFeed/DeleteFeedConfirmation.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedCard.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedLoader.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/NewsFeed/store.ts create mode 100644 apps/dashboard/src/lib/utils/services/pathways/newsfeed/index.ts diff --git a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/DeleteFeedConfirmation.svelte b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/DeleteFeedConfirmation.svelte new file mode 100644 index 000000000..db0eaaeac --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/DeleteFeedConfirmation.svelte @@ -0,0 +1,40 @@ + + + (openDeleteModal = false)} + bind:open={openDeleteModal} + width="w-96" + modalHeading="Delete Feed" +> +
+

Are you sure you want to delete this feed?

+ +
+ (openDeleteModal = false)} + /> + +
+
+
diff --git a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte new file mode 100644 index 000000000..b255fd46c --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte @@ -0,0 +1,143 @@ + + + +
+ { + newPost = text; + }} + placeholder={$t('course.navItem.news_feed.heading_button.placeholder')} + maxHeight={400} + /> + {#if errors.newPost} +

{errors.newPost}

+ {/if} +
+
+ + +
+
+
+
diff --git a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedCard.svelte b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedCard.svelte new file mode 100644 index 000000000..d9dcc9f4f --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedCard.svelte @@ -0,0 +1,241 @@ + + +
+
+
+
+ +
+ users banner +
+ +

{feed.author?.profile?.fullname}

+

{calDateDiff(feed.created_at)}

+
+
+ + + onPin(feed.id, feed.isPinned)} + /> + + (isDeleteFeedModal = true)} /> + + +
+ {#if !isHtmlValueEmpty(feed.content)} + + +
+ {@html feed.content} +
+
+
+ {/if} +
+ +
+
+ {#each Object.keys(feed.reaction) as reactionType} + {#if reactions[reactionType]} + + {/if} + {/each} +
+
+
+ +
+ {#if feed.comment.length > 0} + + {/if} +
+ {#each feed.comment as comment, index} + {#if comment.content && (areCommentsExpanded || index === feed.comment.length - 1)} +
+ +
+ users banner +
+ +
+

{comment.author?.profile?.fullname}

+

{calDateDiff(comment.created_at)}

+
+

{comment.content}

+
+
+ + {#if comment.author?.profile?.id === $profile.id || $isOrgAdmin} + + {/if} +
+ {/if} + {/each} +
+
+
+ users banner +
+
+ +
+ +
+ {#if errors.newComment} +

{errors.newComment}

+ {/if} +
+ +
diff --git a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedLoader.svelte b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedLoader.svelte new file mode 100644 index 000000000..119d2e9e2 --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewsFeedLoader.svelte @@ -0,0 +1,11 @@ + + +
+
+ + +
+ +
diff --git a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/store.ts b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/store.ts new file mode 100644 index 000000000..5a840c1af --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/store.ts @@ -0,0 +1,8 @@ +import type { Feed } from '$lib/utils/types/feed'; +import { writable } from 'svelte/store'; + +export const isNewFeedModal = writable({ + open: false +}); + +export const newsFeed = writable([]); diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte index fc0296346..799d31c59 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte @@ -72,16 +72,20 @@ {/if} - +

- {$t('course.not_permitted.body')} + {$t('pathway.components.not_permitted.body')}

{ goto('/org/*'); }} diff --git a/apps/dashboard/src/lib/components/Pathways/store.ts b/apps/dashboard/src/lib/components/Pathways/store.ts index 67e3c8d41..c8eceb180 100644 --- a/apps/dashboard/src/lib/components/Pathways/store.ts +++ b/apps/dashboard/src/lib/components/Pathways/store.ts @@ -9,7 +9,7 @@ export const addCourseModal = writable({ export const courses = writable([]); export const group = writable({ - id: '', + id: "", tutors: [], students: [], people: [] diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index a138a7987..b174ac7eb 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -1,4 +1,3 @@ -import { STATUS } from '$lib/components/Course/components/Lesson/Exercise/constants'; import { supabase } from '$lib/utils/functions/supabase'; import type { Pathway } from '$lib/utils/types'; import type { PostgrestSingleResponse } from '@supabase/supabase-js'; @@ -11,7 +10,6 @@ export function addPathwayGroupMember(member: any) { const SLUG_QUERY = ` id, title, - type, description, overview, logo, @@ -20,7 +18,6 @@ const SLUG_QUERY = ` cost, currency, metadata, - is_certificate_downloadable, certificate_theme, courses:course( id, title, @@ -30,44 +27,35 @@ const SLUG_QUERY = ` const ID_QUERY = ` id, title, - type, description, - overview, + created_at, + updated_at, + group_id, + is_template, logo, - is_published, - group(*, - members:groupmember(*, - profile(*) - ) - ), slug, + metadata, + settings, cost, currency, - metadata, - is_certificate_downloadable, + banner_image, + is_published, + status, certificate_theme, - courses:course( - id, title,public, lesson_at, is_unlocked, order, created_at, - note, videos, slide_url, call_url, totalExercises:exercise(count), totalComments:lesson_comment(count), - profile:teacher_id(id, avatar_url, fullname), - lesson_completion(id, profile_id, is_complete) - ), - attendance:group_attendance(*), + is_certificate_downloadable `; -export async function fetchPathway(courseId?: Pathway['id'], slug?: Pathway['slug']) { - const match: { slug?: string; id?: string; status?: string } = {}; +export async function fetchPathway(pathwayId?: Pathway['id'], slug?: Pathway['slug']) { + const match: { slug?: string; id?: string } = {}; if (slug) { match.slug = slug; } else { - match.id = courseId; + match.id = pathwayId; } - match.status = STATUS[STATUS.ACTIVE]; - const response: PostgrestSingleResponse = await supabase - .from('course') + .from('pathway') .select(slug ? SLUG_QUERY : ID_QUERY) .match(match) .single(); @@ -78,7 +66,7 @@ export async function fetchPathway(courseId?: Pathway['id'], slug?: Pathway['slu console.log(`data`, data); if (!data || error) { console.log(`data`, data); - console.log(`fetchCourse => error`, error); + console.log(`fetchPathway => error`, error); // return this.redirect(307, '/courses'); return { data, error }; } diff --git a/apps/dashboard/src/lib/utils/services/pathways/newsfeed/index.ts b/apps/dashboard/src/lib/utils/services/pathways/newsfeed/index.ts new file mode 100644 index 000000000..4cb3b1554 --- /dev/null +++ b/apps/dashboard/src/lib/utils/services/pathways/newsfeed/index.ts @@ -0,0 +1,185 @@ +import type { Pathway } from '$lib/utils/types'; +import { supabase } from '$lib/utils/functions/supabase'; +import type { Reaction, FeedApi, Feed } from '$lib/utils/types/feed'; + +export async function fetchNewsFeedReaction(feedId: Feed['id']) { + return supabase.from('pathway_newsfeed').select(`reaction`).eq('id', feedId).single(); +} + +export async function fetchNewsFeeds(pathwayId?: Pathway['id']) { + const response = await supabase + .from('pathway_newsfeed') + .select( + ` + id, + created_at, + content, + pathway_id, + author:groupmember( + profile( + id, + fullname, + avatar_url + ) + ), + reaction, + is_pinned, + comment:pathway_newsfeed_comment( + id, + created_at, + author:groupmember( profile(id, fullname, avatar_url) ), + content, + pathway_newsfeed_id) + ` + ) + .match({ pathway_id: pathwayId }) + .order('created_at', { ascending: false }) + .returns(); + + const { data, error } = response; + + return { data, error }; +} + +export async function createNewFeed(post: { + content: string; + author_id: string; + pathway_id: string; + reaction: Reaction; +}) { + const response = await supabase + .from('pathway_newsfeed') + .insert({ + content: post.content, + author_id: post.author_id, + pathway_id: post.pathway_id, + reaction: post.reaction + }) + .select(); + + return { response }; +} + +export async function handleEditFeed(feedId: string, content: string) { + const response = await supabase + .from('pathway_newsfeed') + .update({ content: content }) + .match({ id: feedId }) + .select(); + return response; +} + +export async function createComment(comment: { + content: string; + author_id: string; + pathway_newsfeed_id: string; +}) { + const response = await supabase + .from('pathway_newsfeed_comment') + .insert({ + content: comment.content, + author_id: comment.author_id, + pathway_newsfeed_id: comment.pathway_newsfeed_id + }) + .select(); + + return { response }; +} + +export async function toggleFeedIsPinned(feedId: string, isPinned: boolean) { + const response = await supabase + .from('pathway_newsfeed') + .update({ + is_pinned: isPinned + }) + .match({ id: feedId }); + + return { response }; +} + +export async function deleteNewsFeedComment(commentId: string) { + const response = await supabase.from('pathway_newsfeed_comment').delete().match({ id: commentId }); + + return response; +} +export async function deleteNewsFeed(feedId: string) { + await supabase.from('pathway_newsfeed_comment').delete().match({ pathway_newsfeed_id: feedId }); + const response = await supabase.from('pathway_newsfeed').delete().match({ id: feedId }); + + return response; +} + +export async function getFeedForNotification(feedId: string, authorId: string) { + const { data, error } = await supabase + .from('pathway_newsfeed') + .select( + ` + content, + author:groupmember(profile(fullname, email)), + pathway( + id, + title, + group( + organization(siteName, name), + members:groupmember(id, profile(email, fullname)) + ) + ) + ` + ) + .eq('id', feedId) + .limit(1) + .returns< + { + content: string; + author: { + profile: { + email: string; + fullname: string; + }; + }; + pathway: { + id: string; + title: string; + group: { + organization: { + name: string; + siteName: string; + }; + members: { + id: string; + profile: { + email: string; + fullname: string; + }; + }[]; + }; + }; + }[] + >(); + + if (error) { + console.error('Failed to get feed', error); + return null; + } + console.log({ + data + }); + const [feed] = data || []; + + if (!feed) return; + + return { + id: feedId, + pathwayId: feed.pathway.id, + pathwayTitle: feed.pathway.title, + teacherName: feed.author?.profile?.fullname, + teacherEmail: feed.author?.profile?.email, + content: feed.content, + org: feed.pathway.group?.organization, + pathwayMembers: feed.pathway?.group?.members + ?.filter((member) => member.id !== authorId) + ?.map((member) => { + return member.profile; + }) + }; +} diff --git a/apps/dashboard/src/lib/utils/translations/de.json b/apps/dashboard/src/lib/utils/translations/de.json index e05656517..b8b1c2a1f 100644 --- a/apps/dashboard/src/lib/utils/translations/de.json +++ b/apps/dashboard/src/lib/utils/translations/de.json @@ -1456,6 +1456,16 @@ "course": "Kurs", "courses": "Kurse", "path": "zum Lernpfad" + }, + "not_permitted": { + "header": "Keine Erlaubnis", + "body": "Leider haben Sie keinen Zugriff auf diesen Weg.", + "button": "Nach Hause gehen" + }, + "dragAndDrop": { + "title": "Kurse organisieren", + "header": "Ordnen Sie den Kurs in der Reihenfolge an, in der der Student ihn absolvieren muss, und beginnen Sie mit dem ersten ganz oben", + "label": "Kursbestellung bestätigen" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "Titel", "description": "Beschreibung", "students": "Studenten", - "lessons": "Unterricht" + "lessons": "Unterricht", + "empty_state_header": "Noch keine Kurse", + "empty_state_text": "Sie haben diesem Lernpfad keinen Kurs hinzugefügt", + "empty_state_label": "Kurs zu diesem Lernpfad hinzufügen" } } }, "search": { "no_pathway": "Kein Weg gefunden", "no_course": "Kein Kurs gefunden" + }, + "lms_pathway": { + "pathway": "WEG", + "continue": "Pfad fortsetzen", + "course": "Kurse", + "locked": "Gesperrt", + "goto_pathway": "Gehen Sie zu Pathway", + "completed": "Vollendet", + "of": "von" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/translations/en.json b/apps/dashboard/src/lib/utils/translations/en.json index 95587fb32..4cab0c08f 100644 --- a/apps/dashboard/src/lib/utils/translations/en.json +++ b/apps/dashboard/src/lib/utils/translations/en.json @@ -117,6 +117,11 @@ "title": "Arrange courses", "header": "Arrange course in the sequence student must should take them start with first at the top", "label": "Confirm course order" + }, + "not_permitted": { + "header": "No Permission", + "body": "Unfortunately you don't have access to this pathway.", + "button": "Go home" } } }, @@ -1317,7 +1322,7 @@ "add": "New feed added successfully" }, "error": { - "error": "An error occurred while", + "error": "An error occurred while ", "editing": "editing feed", "creating": "creating feed" } diff --git a/apps/dashboard/src/lib/utils/translations/es.json b/apps/dashboard/src/lib/utils/translations/es.json index 5faf1aa21..8822c9677 100644 --- a/apps/dashboard/src/lib/utils/translations/es.json +++ b/apps/dashboard/src/lib/utils/translations/es.json @@ -1456,6 +1456,16 @@ "course": "Curso", "courses": "Cursos", "path": "al camino del aprendizaje" + }, + "not_permitted": { + "header": "Sin permiso", + "body": "Lamentablemente no tienes acceso a esta vía.", + "button": "Ir a casa" + }, + "dragAndDrop": { + "title": "Organizar cursos", + "header": "Organice el curso en la secuencia que el estudiante debe tomar, comience primero en la parte superior", + "label": "Confirmar orden del curso" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "Título", "description": "Descripción", "students": "Estudiantes", - "lessons": "Lecciones" + "lessons": "Lecciones", + "empty_state_header": "Aún no hay cursos", + "empty_state_text": "No has agregado ningún curso a esta ruta de aprendizaje.", + "empty_state_label": "Agregar curso a esta ruta de aprendizaje" } } }, "search": { "no_pathway": "No se encontró ningún camino", "no_course": "No se encontró ningún curso" + }, + "lms_pathway": { + "pathway": "CAMINO", + "continue": "Continuar camino", + "course": "Cursos", + "locked": "bloqueado", + "goto_pathway": "Ir al camino", + "completed": "Terminado", + "of": "de" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/translations/fr.json b/apps/dashboard/src/lib/utils/translations/fr.json index 735ed6cc5..40dca0d54 100644 --- a/apps/dashboard/src/lib/utils/translations/fr.json +++ b/apps/dashboard/src/lib/utils/translations/fr.json @@ -1456,6 +1456,16 @@ "course": "Cours", "courses": "Cours", "path": "au parcours d'apprentissage" + }, + "not_permitted": { + "header": "Aucune autorisation", + "body": "Malheureusement, vous n'avez pas accès à ce parcours.", + "button": "Rentre chez toi" + }, + "dragAndDrop": { + "title": "Organiser des cours", + "header": "Organiser les cours dans l'ordre que l'étudiant doit suivre en commençant par le premier en haut", + "label": "Confirmer l'ordre des cours" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "Titre", "description": "Description", "students": "Étudiants", - "lessons": "Cours" + "lessons": "Cours", + "empty_state_header": "Pas encore de cours", + "empty_state_text": "Vous n'avez ajouté aucun cours à ce parcours d'apprentissage", + "empty_state_label": "Ajouter un cours à ce parcours d'apprentissage" } } }, "search": { "no_pathway": "Aucun chemin trouvé", "no_course": "Aucun cours trouvé" + }, + "lms_pathway": { + "pathway": "CHEMIN", + "continue": "Continuer le chemin", + "course": "Cours", + "locked": "Fermé", + "goto_pathway": "Aller au parcours", + "completed": "Complété", + "of": "de" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/translations/hi.json b/apps/dashboard/src/lib/utils/translations/hi.json index 29d3654e1..f0b703c97 100644 --- a/apps/dashboard/src/lib/utils/translations/hi.json +++ b/apps/dashboard/src/lib/utils/translations/hi.json @@ -1456,6 +1456,16 @@ "course": "अवधि", "courses": "पाठ्यक्रम", "path": "सीखने के पथ के लिए" + }, + "not_permitted": { + "header": "कोई अनुमति नहीं", + "body": "दुर्भाग्य से आपके पास इस मार्ग तक पहुंच नहीं है।", + "button": "घर जाओ" + }, + "dragAndDrop": { + "title": "पाठ्यक्रम व्यवस्थित करें", + "header": "पाठ्यक्रम को उस क्रम में व्यवस्थित करें जिसमें विद्यार्थी को सबसे पहले सबसे ऊपर से शुरुआत करनी चाहिए", + "label": "पाठ्यक्रम आदेश की पुष्टि करें" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "शीर्षक", "description": "विवरण", "students": "छात्र", - "lessons": "पाठ" + "lessons": "पाठ", + "empty_state_header": "अभी तक कोई पाठ्यक्रम नहीं", + "empty_state_text": "आपने इस शिक्षण पथ में कोई पाठ्यक्रम नहीं जोड़ा है", + "empty_state_label": "इस शिक्षण पथ में पाठ्यक्रम जोड़ें" } } }, "search": { "no_pathway": "कोई मार्ग नहीं मिला", "no_course": "कोई पाठ्यक्रम नहीं मिला" + }, + "lms_pathway": { + "pathway": "मार्ग", + "continue": "पथ जारी रखें", + "course": "पाठ्यक्रम", + "locked": "बंद", + "goto_pathway": "पाथवे पर जाएं", + "completed": "पुरा होना।", + "of": "का" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/translations/pt.json b/apps/dashboard/src/lib/utils/translations/pt.json index 82ef2d964..103759164 100644 --- a/apps/dashboard/src/lib/utils/translations/pt.json +++ b/apps/dashboard/src/lib/utils/translations/pt.json @@ -1456,6 +1456,16 @@ "course": "Curso", "courses": "Cursos", "path": "para o caminho de aprendizagem" + }, + "not_permitted": { + "header": "Sem permissão", + "body": "Infelizmente você não tem acesso a esse caminho.", + "button": "Ir para casa" + }, + "dragAndDrop": { + "title": "Organizar cursos", + "header": "Organize o curso na sequência que o aluno deve fazer, começando com o primeiro no topo", + "label": "Confirmar pedido do curso" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "Título", "description": "Descrição", "students": "Alunos", - "lessons": "Lições" + "lessons": "Lições", + "empty_state_header": "Ainda não há cursos", + "empty_state_text": "Você não adicionou nenhum curso a este caminho de aprendizagem", + "empty_state_label": "Adicionar curso a este caminho de aprendizagem" } } }, "search": { "no_pathway": "Nenhum caminho encontrado", "no_course": "Nenhum curso encontrado" + }, + "lms_pathway": { + "pathway": "CAMINHO", + "continue": "Continuar caminho", + "course": "Cursos", + "locked": "Bloqueado", + "goto_pathway": "Vá para o caminho", + "completed": "Concluído", + "of": "de" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/translations/ru.json b/apps/dashboard/src/lib/utils/translations/ru.json index f568783e3..c79e5ff13 100644 --- a/apps/dashboard/src/lib/utils/translations/ru.json +++ b/apps/dashboard/src/lib/utils/translations/ru.json @@ -1456,6 +1456,16 @@ "course": "Курс", "courses": "Курсы", "path": "к пути обучения" + }, + "not_permitted": { + "header": "Нет разрешения", + "body": "К сожалению, у вас нет доступа к этому пути.", + "button": "Иди домой" + }, + "dragAndDrop": { + "title": "Организовать курсы", + "header": "Расположите курс в той последовательности, в которой студент должен его пройти, начиная с первого вверху.", + "label": "Подтвердить заказ курса" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "Заголовок", "description": "Описание", "students": "Студенты", - "lessons": "Уроки" + "lessons": "Уроки", + "empty_state_header": "Курсов пока нет", + "empty_state_text": "Вы не добавили ни одного курса в этот путь обучения.", + "empty_state_label": "Добавить курс в этот путь обучения" } } }, "search": { "no_pathway": "Путь не найден", "no_course": "Курс не найден" + }, + "lms_pathway": { + "pathway": "ПУТЬ", + "continue": "Продолжить путь", + "course": "Курсы", + "locked": "Заблокировано", + "goto_pathway": "Перейти к Пути", + "completed": "Завершенный", + "of": "из" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/translations/vi.json b/apps/dashboard/src/lib/utils/translations/vi.json index cfb025640..25b72b585 100644 --- a/apps/dashboard/src/lib/utils/translations/vi.json +++ b/apps/dashboard/src/lib/utils/translations/vi.json @@ -1456,6 +1456,16 @@ "course": "Khóa học", "courses": "Khóa học", "path": "đến lộ trình học tập" + }, + "not_permitted": { + "header": "Không có quyền", + "body": "Rất tiếc, bạn không có quyền truy cập vào con đường này.", + "button": "Về nhà" + }, + "dragAndDrop": { + "title": "Sắp xếp các khóa học", + "header": "Sắp xếp khóa học theo trình tự học sinh phải học bắt đầu với phần đầu tiên ở trên cùng", + "label": "Xác nhận thứ tự khóa học" } }, "pages": { @@ -1503,12 +1513,24 @@ "body_title": "Tiêu đề", "description": "Sự miêu tả", "students": "Sinh viên", - "lessons": "Những bài học" + "lessons": "Những bài học", + "empty_state_header": "Chưa có khóa học nào", + "empty_state_text": "Bạn chưa thêm bất kỳ khóa học nào vào lộ trình học tập này", + "empty_state_label": "Thêm khóa học vào lộ trình học tập này" } } }, "search": { "no_pathway": "Không tìm thấy con đường nào", "no_course": "Không tìm thấy khóa học" + }, + "lms_pathway": { + "pathway": "LỘ TRÌNH", + "continue": "Tiếp tục con đường", + "course": "Khóa học", + "locked": "Đã khóa", + "goto_pathway": "Đi đến Con đường", + "completed": "Hoàn thành", + "of": "của" } -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/types/org.ts b/apps/dashboard/src/lib/utils/types/org.ts index 4e716ba6b..56aa2baf6 100644 --- a/apps/dashboard/src/lib/utils/types/org.ts +++ b/apps/dashboard/src/lib/utils/types/org.ts @@ -9,6 +9,9 @@ export interface OrgCustomization { newsfeed: boolean; grading: boolean; }; + pathway: { + newsfeed: boolean; + }; apps: { poll: boolean; comments: boolean; diff --git a/apps/dashboard/src/routes/pathways/[id]/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/+page.svelte index 5733a03af..e12972610 100644 --- a/apps/dashboard/src/routes/pathways/[id]/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/+page.svelte @@ -11,7 +11,7 @@ handleEditFeed, toggleFeedIsPinned, fetchNewsFeedReaction - } from '$lib/utils/services/newsfeed'; + } from '$lib/utils/services/pathways/newsfeed'; import { t } from '$lib/utils/functions/translations'; import { NOTIFICATION_NAME, @@ -21,20 +21,20 @@ import { profile } from '$lib/utils/store/user'; import { currentOrg } from '$lib/utils/store/org'; - import { group } from '$lib/components/Course/store'; + import { group } from '$lib/components/Pathways/store'; import { snackbar } from '$lib/components/Snackbar/store'; - import { newsFeed } from '$lib/components/Course/components/NewsFeed/store'; + import { newsFeed } from '$lib/components/Pathways/components/NewsFeed/store.js'; import Box from '$lib/components/Box/index.svelte'; import PageNav from '$lib/components/PageNav/index.svelte'; import PageBody from '$lib/components/PageBody/index.svelte'; import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte'; - import { isNewFeedModal } from '$lib/components/Course/components/NewsFeed/store'; - import NewsFeedCard from '$lib/components/Course/components/NewsFeed/NewsFeedCard.svelte'; - import NewFeedModal from '$lib/components/Course/components/NewsFeed/NewFeedModal.svelte'; + import { isNewFeedModal } from '$lib/components/Pathways/components/NewsFeed/store'; + import NewsFeedCard from '$lib/components/Pathways/components/NewsFeed/NewsFeedCard.svelte'; + import NewFeedModal from '$lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte'; import PathwayContainer from '$lib/components/Pathways/components/PathwayContainer.svelte'; - import NewsFeedLoader from '$lib/components/Course/components/NewsFeed/NewsFeedLoader.svelte'; + import NewsFeedLoader from '$lib/components/Pathways/components/NewsFeed/NewsFeedLoader.svelte'; import type { Feed } from '$lib/utils/types/feed'; import type { CurrentOrg } from '$lib/utils/types/org'; @@ -100,7 +100,7 @@ }; const response = await supabase - .from('course_newsfeed') + .from('pathway_newsfeed') .update({ reaction: reactedFeed.reaction }) @@ -123,7 +123,7 @@ const { response } = await createComment({ content: comment, author_id: authorId, - course_newsfeed_id: feedId + pathway_newsfeed_id: feedId }); if (response.error) { @@ -168,15 +168,17 @@ const { response } = await toggleFeedIsPinned(feedId, newIsPinned); if (response.error) { - return snackbar.error('snackbar.course.error.toggle_error'); + return snackbar.error(`${$t('snackbar.course.error.toggle_error')}`); } $newsFeed = $newsFeed.map((feed) => { if (feed.id === feedId) { snackbar.success( `${ - newIsPinned ? 'snackbar.course.success.pinned' : 'snackbar.course.success.unpinned' - } snackbar.course.success.successfully` + newIsPinned + ? $t('snackbar.course.success.pinned') + : $t('snackbar.course.success.unpinned') + } ${$t('snackbar.course.success.successfully')}` ); feed.isPinned = newIsPinned; @@ -196,11 +198,11 @@ $newsFeed = deletedFeed; }; - const initNewsFeed = async (courseId: string) => { - if (!courseId) return; + const initNewsFeed = async (pathwayId: string) => { + if (!pathwayId) return; try { isLoading = true; - const { data, error } = await fetchNewsFeeds(courseId); + const { data, error } = await fetchNewsFeeds(pathwayId); if (error) { snackbar.error('snackbar.course.error.news_feed_fail'); } @@ -219,7 +221,6 @@ function setAuthor(groups, profileId) { const currentGroupMember = groups.people.find((person) => person.profile_id === profileId); - author = { id: currentGroupMember?.id || '', username: $profile.username || '', @@ -239,7 +240,6 @@ } $: initNewsFeed(data.pathwayId); - $: setAuthor($group, $profile.id); $: $newsFeed = $newsFeed.sort((a, b) => Number(b.isPinned) - Number(a.isPinned)); @@ -266,7 +266,7 @@ Date: Tue, 20 Aug 2024 09:15:29 +0100 Subject: [PATCH 03/22] feat: courses page --- .../Pathways/components/AddCourseModal.svelte | 69 ++++++-- .../components/Certificate/Design.svelte | 10 +- .../components/DragAndDropModal.svelte | 43 +++-- .../components/PathwayContainer.svelte | 1 - .../Pathways/components/Sidebar.svelte | 10 +- .../src/lib/components/Pathways/store.ts | 154 ++---------------- .../utils/services/pathways/courses/index.ts | 29 ++++ .../src/lib/utils/services/pathways/index.ts | 68 ++++++-- apps/dashboard/src/lib/utils/types/index.ts | 43 +---- .../pathways/[id]/certificates/+page.svelte | 2 +- .../routes/pathways/[id]/courses/+page.svelte | 42 ++--- .../routes/pathways/[id]/people/+page.svelte | 4 +- .../pathways/[id]/settings/+page.svelte | 4 +- 13 files changed, 222 insertions(+), 257 deletions(-) create mode 100644 apps/dashboard/src/lib/utils/services/pathways/courses/index.ts diff --git a/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte index 44fcc5753..1f3219cdf 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte @@ -7,23 +7,29 @@ StructuredListRow, Search } from 'carbon-components-svelte'; - import IconButton from '$lib/components/IconButton/index.svelte'; - import ArrowLeft from 'carbon-icons-svelte/lib/ArrowLeft.svelte'; + + import { courses } from '../store'; + import { addCourseModal } from '../store'; import { profile } from '$lib/utils/store/user'; import { currentOrg } from '$lib/utils/store/org'; - import { addCourseModal, courses } from '../store'; + import type { Course, PathwayCourse } from '$lib/utils/types'; import { t } from '$lib/utils/functions/translations'; import { fetchCourses } from '$lib/utils/services/courses'; + import { addPathwayCourse } from '$lib/utils/services/pathways'; + import Modal from '$lib/components/Modal/index.svelte'; + import DragAndDropModal from './DragAndDropModal.svelte'; import Checkbox from '$lib/components/Form/Checkbox.svelte'; + import IconButton from '$lib/components/IconButton/index.svelte'; + import ArrowLeft from 'carbon-icons-svelte/lib/ArrowLeft.svelte'; import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; - import DragAndDropModal from './DragAndDropModal.svelte'; - import type { PathwayCourse } from '$lib/utils/types'; + export let pathwayId; - let pathwayCourses: PathwayCourse[] = []; let searchValue = ''; let isSearchInputOpened = false; + let allCourses: Course[] = []; + let pathwayCourses: PathwayCourse[] = []; function close() { $addCourseModal.open = false; @@ -31,12 +37,39 @@ } function handleSave() { + courses.update((existingCourses) => { + const currentCourseCount = existingCourses.length; + + const newCourses = pathwayCourses + .filter((course) => !existingCourses.some((c) => c.course_id === course.id)) + .map((course, index) => ({ + id: '', + course: course, + course_id: course.id, + pathway_id: pathwayId || '', + order: currentCourseCount + index + 1 + })); + + newCourses.forEach(async (newCourse) => { + try { + await addPathwayCourse(newCourse.pathway_id, newCourse.course_id); + } catch (error) { + console.error('Error updating course in Supabase:', error); + } + }); + + return [...existingCourses, ...newCourses]; + }); + $addCourseModal.step = 1; } function toggleCourseSelection(course: PathwayCourse, checked: boolean) { if (checked) { - pathwayCourses = [...pathwayCourses, course]; + // avoid duplicating the course + if (!pathwayCourses.some((c) => c.id === course.id)) { + pathwayCourses = [...pathwayCourses, course]; + } } else { pathwayCourses = pathwayCourses.filter((c) => c.id !== course.id); } @@ -44,16 +77,21 @@ async function fetchCourse(userId?: string, orgId?: string) { const data = await fetchCourses(userId, orgId); + if (!data) return; + allCourses = data.allCourses; - courses.set(data.allCourses); + // filter out courses that already exist in $courses + allCourses = data.allCourses.filter( + (course) => !$courses.some((c) => c.course?.id === course.id) + ); } $: fetchCourse($profile.id, $currentOrg.id); - $: filteredCourses = $courses.filter((course) => - course.title.toLowerCase().includes(searchValue.toLowerCase()) - ); + $: filteredCourses = searchValue + ? allCourses.filter((course) => course.title.toLowerCase().includes(searchValue.toLowerCase())) + : allCourses; {#if $addCourseModal.step === 1} - ($addCourseModal.step = 0)}> + ($addCourseModal.step = 0)}> {/if} @@ -105,7 +143,7 @@ name={course.title} className="cursor-pointer" value={course.id} - checked={pathwayCourses.includes(course)} + checked={pathwayCourses.some((c) => c.id === course.id)} onInputChange={(e) => toggleCourseSelection(course, e.target?.checked)} />
@@ -135,7 +173,10 @@
{:else if $addCourseModal.step === 1} - (pathwayCourses = e.detail)} /> + (pathwayCourses = e.detail)} + /> {/if}
diff --git a/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte b/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte index a37ec60ba..9e3ee8263 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte @@ -7,6 +7,7 @@ import { globalStore } from '$lib/utils/store/app'; import { t } from '$lib/utils/functions/translations'; import { currentOrg, isFreePlan } from '$lib/utils/store/org'; + import { updatePathway } from '$lib/utils/services/pathways/courses'; import { saveCertificateValidation } from '$lib/utils/functions/validator'; import { snackbar } from '$lib/components/Snackbar/store'; @@ -38,7 +39,7 @@ }; let helperText = ''; - const saveCertificate = () => { + const saveCertificate = async () => { isSaving = true; console.log('pathway', $pathway); @@ -57,10 +58,13 @@ } errors.description = ''; - console.log('pathway', $pathway); // TODO: save pathway - + await updatePathway($pathway.id, undefined, { + description: $pathway.description || '', + is_certificate_downloadable: $pathway.is_certificate_downloadable || false, + certificate_theme: $pathway.certificate_theme || '' + }); snackbar.success('snackbar.course_settings.success.saved'); } catch (error) { if (error.message) { diff --git a/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte index d4602e47b..4e9ffc069 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte @@ -1,39 +1,44 @@ - +
- +
@@ -112,7 +116,7 @@
- {#if filteredCourses.length > 0} + {#if $courses.length > 0} {#if coursePreference === 'list'}
@@ -134,31 +138,31 @@

- {course.title} + {course.course.title}

- {course.description} + {course.course.description} - {course.total_students} + {course.course.group?.members.length} - {course.total_lessons} + {course.course.lessons?.length}
{/each}
- {:else} + {:else if coursePreference === 'grid'}
{#each filteredCourses as course} {/each}
diff --git a/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte index 63250625b..83260ec1e 100644 --- a/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte @@ -1,8 +1,10 @@ - +

People page o!

diff --git a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte index e23d44864..e45f8953a 100644 --- a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte @@ -28,6 +28,8 @@ import CustomToggle from '$lib/components/Pathways/components/Toggle.svelte'; import PathwayContainer from '$lib/components/Pathways/components/PathwayContainer.svelte'; + export let data; + let id: string; let isSaving = false; let slug: boolean = true; @@ -83,7 +85,7 @@ $: id = $page.params.id; - +
From 44f9b3f1bac2cad4c933473923d1847784f14932 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Tue, 20 Aug 2024 09:39:24 +0100 Subject: [PATCH 04/22] merge fixes --- .../components/PathwayContainer.svelte | 35 --- .../src/lib/components/Pathways/store.ts | 2 +- .../src/lib/utils/services/pathways/index.ts | 5 - apps/dashboard/src/lib/utils/types/index.ts | 3 - .../src/routes/pathways/[id]/+page.svelte | 208 ------------------ .../pathways/[id]/settings/+page.svelte | 2 +- 6 files changed, 2 insertions(+), 253 deletions(-) diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte index 594a8a865..6acb446d7 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte @@ -1,6 +1,4 @@ - + From 72e0f4f0b17773889ce252f14546f71fef9b9728 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Tue, 20 Aug 2024 21:55:22 +0100 Subject: [PATCH 05/22] checkpoint: people's page completion --- .../UnlockedCertificate.svelte | 2 +- .../components/PricingSection.svelte | 26 +- .../PathwayLandingPage/index.svelte | 10 +- .../People/DeleteConfirmation.svelte | 44 +++ .../components/People/InvitationModal.svelte | 281 ++++++++++++++++++ .../components/People/ShareQRImage.svelte | 35 +++ .../Pathways/components/People/store.js | 7 + .../Pathways/components/People/types.ts | 21 ++ .../src/lib/components/Pathways/store.ts | 173 +---------- .../src/lib/utils/functions/pathway.ts | 31 ++ .../src/lib/utils/services/courses/index.ts | 27 -- .../services/notification/notification.ts | 2 + .../src/lib/utils/services/pathways/index.ts | 26 +- .../src/routes/api/email/pathway/+server.js | 53 ++++ .../routes/pathways/[id]/people/+page.svelte | 8 +- 15 files changed, 537 insertions(+), 209 deletions(-) create mode 100644 apps/dashboard/src/lib/components/Pathways/components/People/DeleteConfirmation.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/People/InvitationModal.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/People/ShareQRImage.svelte create mode 100644 apps/dashboard/src/lib/components/Pathways/components/People/store.js create mode 100644 apps/dashboard/src/lib/components/Pathways/components/People/types.ts create mode 100644 apps/dashboard/src/lib/utils/functions/pathway.ts create mode 100644 apps/dashboard/src/routes/api/email/pathway/+server.js diff --git a/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte b/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte index 2511b2798..dce2f648e 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte @@ -15,7 +15,7 @@ import { pathway } from '$lib/components/Pathways/store'; import type { ProfilePathwayProgress } from '$lib/utils/types'; - import { fetchPathwayCourseProgress } from '$lib/utils/services/pathways'; + import { fetchProfilePathwayProgress } from '$lib/utils/services/pathways'; let isLoading = false; let showCourses = false; diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/PricingSection.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/PricingSection.svelte index 98b98feae..aabbc3f17 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/PricingSection.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/PricingSection.svelte @@ -64,11 +64,11 @@ $: setFormatter(pathwayData.currency); - $: discount = get(pathwayData, 'metadata.discount', 0); + $: discount = get(pathwayData, 'landingpage.discount', 0); $: calculatedCost = calcDisc( discount, pathwayData.cost || 0, - !!pathwayData.metadata.showDiscount + !!pathwayData.landingpage.showDiscount ); $: isFree = isCourseFree(calculatedCost); $: startCoursePayment && handleJoinCourse(); @@ -76,7 +76,7 @@ @@ -94,7 +94,7 @@
- {#if pathwayData?.metadata?.allowNewStudent} + {#if pathwayData?.landingpage?.allowNewStudent}

{formatter?.format(calculatedCost) || calculatedCost} {#if isFree} @@ -103,7 +103,7 @@ > {/if}

- {#if pathwayData?.metadata?.showDiscount} + {#if pathwayData?.landingpage?.showDiscount}

{discount}% {$t('course.navItem.landing_page.pricing_section.discount')}.

@@ -137,7 +137,7 @@
- {#if pathwayData?.metadata?.allowNewStudent} + {#if pathwayData?.landingpage?.allowNewStudent}

{formatter?.format(calculatedCost) || calculatedCost} {#if isFree} @@ -145,7 +145,7 @@ > {/if}

- {#if pathwayData?.metadata?.showDiscount} + {#if pathwayData?.landingpage?.showDiscount}

{discount}% {$t('course.navItem.landing_page.pricing_section.discount')}. - {#if pathwayData?.metadata?.showDiscount && pathwayData.metadata.allowNewStudent} + {#if pathwayData?.landingpage?.showDiscount && pathwayData.landingpage.allowNewStudent}

{$t('course.navItem.landing_page.pricing_section.bird')}

@@ -196,9 +196,9 @@
- {#if pathwayData?.metadata?.reward?.show} + {#if pathwayData?.landingpage?.reward?.show}
- {@html get(pathwayData, 'metadata.reward.description', '')} + {@html get(pathwayData, 'landingpage.reward.description', '')}
{/if} diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte index 8e2cc2fc6..0e901835a 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte @@ -198,7 +198,7 @@

- {$pathway.courses.length} + {$pathway.pathway_course.length} {$t('pathway.pages.landingPage.banner.courses')}

@@ -313,7 +313,7 @@ '' )}" className="md:w-[30%] w-full py-3 mb-3 font-semibold" - isDisabled={!pathwayData.metadata.allowNewStudent} + isDisabled={!pathwayData.landingpage.allowNewStudent} />

@@ -330,15 +330,15 @@
- {#each $pathway.courses as course} + {#each $pathway.pathway_course as course}
-

{course.title}

+

{course.course.title}

- {course.total_lessons} + {course.course.lesson?.length || 0} {$t('pathway.pages.landingPage.courseList.lessons')} - diff --git a/apps/dashboard/src/lib/components/Pathways/components/People/DeleteConfirmation.svelte b/apps/dashboard/src/lib/components/Pathways/components/People/DeleteConfirmation.svelte new file mode 100644 index 000000000..a74a304af --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/People/DeleteConfirmation.svelte @@ -0,0 +1,44 @@ + + + ($deleteMemberModal.open = false)} + bind:open={$deleteMemberModal.open} + width="w-96" + modalHeading={$t('course.navItem.people.delete_confirmation.title')} +> +
+

+ {$t('course.navItem.people.delete_confirmation.sure')} {email}? +

+ +
+ ($deleteMemberModal.open = false)} + /> + +
+
+
diff --git a/apps/dashboard/src/lib/components/Pathways/components/People/InvitationModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/People/InvitationModal.svelte new file mode 100644 index 000000000..c7fd9d9e9 --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/People/InvitationModal.svelte @@ -0,0 +1,281 @@ + + + closeModal()} + open={addPeopleParm === 'true'} + width="w-4/5 md:w-2/5" + maxWidth="max-w-lg" + modalHeading={$t('course.navItem.people.invite_modal.title')} +> +
+
+

{$t('course.navItem.people.invite_modal.invite')}

+ + {#if isLoadingTutors} + + + + {:else} + + {$t('course.navItem.people.invite_modal.to_add')} + + {$t('course.navItem.people.invite_modal.go_to')} + + + {/if} +
+ +
+
+

+ {$t('course.navItem.people.invite_modal.invite_students')} +

+

{$t('course.navItem.people.invite_modal.via_link')}

+
+ +
+ + + +
{$t('course.navItem.people.invite_modal.success')}
+
+
+
+ +
+
+ + {$t('course.navItem.people.invite_modal.via_qr')} + + + +
+ +
+ link qrcode +
+
+ +
+ +
+ +
+ +
+
+
diff --git a/apps/dashboard/src/lib/components/Pathways/components/People/ShareQRImage.svelte b/apps/dashboard/src/lib/components/Pathways/components/People/ShareQRImage.svelte new file mode 100644 index 000000000..1578ef53a --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/People/ShareQRImage.svelte @@ -0,0 +1,35 @@ + + +
+
+
Scan QR
+ qrcode +
+

{pathway.title}

+

{currentOrg.name}

+
+
+ {#if $isFreePlan} +
+ logo + ClassroomIO.com +
+ {/if} +
diff --git a/apps/dashboard/src/lib/components/Pathways/components/People/store.js b/apps/dashboard/src/lib/components/Pathways/components/People/store.js new file mode 100644 index 000000000..c7b637c33 --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/People/store.js @@ -0,0 +1,7 @@ +import { writable } from 'svelte/store'; + +export const deleteMemberModal = writable({ + open: false +}); + +export const qrInviteNodeStore = writable(null); diff --git a/apps/dashboard/src/lib/components/Pathways/components/People/types.ts b/apps/dashboard/src/lib/components/Pathways/components/People/types.ts new file mode 100644 index 000000000..0791158fc --- /dev/null +++ b/apps/dashboard/src/lib/components/Pathways/components/People/types.ts @@ -0,0 +1,21 @@ +export interface Profile { + id: string; + fullname: string; + username: string; + email: string; + avatar_url?: string; +} + +export interface Person { + id: string; + email?: string; + role_id: number; + profile?: Profile; + profile_id?: string; + assigned_student_id: string; +} + +export interface ProfileRole { + label: string; + value: string | number; +} diff --git a/apps/dashboard/src/lib/components/Pathways/store.ts b/apps/dashboard/src/lib/components/Pathways/store.ts index b4c46df41..c60ef5f79 100644 --- a/apps/dashboard/src/lib/components/Pathways/store.ts +++ b/apps/dashboard/src/lib/components/Pathways/store.ts @@ -1,5 +1,6 @@ import { writable } from 'svelte/store'; import type { Pathway, PathwayCourse, GroupStore } from '$lib/utils/types'; +import { ROLE } from '$lib/utils/constants/roles'; export const RADIO_VALUE = { TRUE: 'true', @@ -32,22 +33,22 @@ export const group = writable({ }); export const defaultPathway: Pathway = { - id: 'pathway-one', - slug: 'pathway-one', - title: 'Pathway One', + id: '', + slug: '', + title: '', description: - 'Create, Launch & Market Your Own Online Business w/ Digital Products such as Online Courses, Coaching, eBooks, Webinars+', + '', prerequisite: '', is_published: false, lms_certificate: false, courses_certificate: RADIO_VALUE.FALSE, - cost: 3000, - currency: 'NGN', + cost: 0, + currency: '', status: 'Published', - enrollment_date: 'May 2', - created_at: '20, March, 2024', - updated_at: '20, March, 2024', - metadata: { + created_at: new Date().toDateString(), + updated_at: new Date().toDateString(), + enrollment_date: '', + landingpage: { header: { title: 'Online Business Masterclass: Sell Your Own Digital Products', description: @@ -112,139 +113,7 @@ export const defaultPathway: Pathway = { }, is_certificate_downloadable: false, certificate_theme: 'professional', - courses: [ - { - id: '4653bss37a-b0c4-4cf1-9dab-1ec4614a8643', - banner_image: '/images/org-landingpage-our-story.jpeg', - title: 'Basic Fundamental of graphic design', - description: - 'Begin with rudiment of graphic design including typography, layouts, colours.....', - total_lessons: 5, - total_students: 30, - estimated_hours: 3, - is_unlocked: true, - is_completed: [ - { - id: 1, - course_id: '4653b37a-b0c4-4cf1-9dab-1ec4614a8643', - profile_id: '', - is_complete: true, - created_at: '', - updated_at: '' - } - ], - is_published: false, - created_at: '', - - updated_at: '', - order: '' - }, - { - id: '73f9ascas2bda-f306-4c7b-88d3-d3a4ed37fb06', - banner_image: '/images/org-landingpage-our-story.jpeg', - title: 'Establishing hierachy', - description: - 'Begin with rudiment of graphic design including typography, layouts, colours.....', - total_lessons: 5, - total_students: 10, - estimated_hours: 3, - is_unlocked: false, - is_completed: [ - { - id: 1, - course_id: '73f92bda-f306-4c7b-88d3-d3a4ed37fb06', - profile_id: '', - is_complete: true, - created_at: '', - updated_at: '' - } - ], - is_published: false, - created_at: '', - - updated_at: '', - order: '' - }, - { - id: '41afdjmh56e-938c-45be-8f71-e59465dacce1', - banner_image: '/images/org-landingpage-our-story.jpeg', - title: 'Empathy', - description: - 'Begin with rudiment of graphic design including typography, layouts, colours.....', - total_lessons: 5, - total_students: 3, - estimated_hours: 3, - is_unlocked: false, - is_completed: [ - { - id: 1, - course_id: '41afd56e-938c-45be-8f71-e59465dacce1', - profile_id: '', - is_complete: true, - created_at: '', - updated_at: '' - } - ], - is_published: false, - created_at: '', - - updated_at: '', - order: '' - }, - { - id: 'ef15evr6ee-018d-48ab-a195-8030366aae06', - banner_image: '/images/org-landingpage-our-story.jpeg', - title: 'Learn Typography', - description: - 'Begin with rudiment of graphic design including typography, layouts, colours.....', - total_lessons: 5, - total_students: 2, - estimated_hours: 3, - is_unlocked: false, - is_completed: [ - { - id: 1, - course_id: 'ef15e6ee-018d-48ab-a195-8030366aae06', - profile_id: '', - is_complete: true, - created_at: '', - updated_at: '' - } - ], - is_published: true, - created_at: '', - - updated_at: '', - order: '' - }, - { - id: 'ef15egcg6ee-018d-48ab-a195-8030366aae06', - banner_image: '/images/org-landingpage-our-story.jpeg', - title: 'Learn colours', - description: - 'Begin with rudiment of graphic design including typography, layouts, colours.....', - total_lessons: 5, - total_students: 30, - estimated_hours: 3, - is_unlocked: false, - is_completed: [ - { - id: 1, - course_id: 'ef15e6ee-018d-48ab-a195-8030366aae06', - profile_id: '', - is_complete: true, - created_at: '', - updated_at: '' - } - ], - is_published: true, - created_at: '', - - updated_at: '', - order: '' - } - ], - selectedCourses: [] + pathway_course: [] }; export const pathway = writable({ ...defaultPathway }); @@ -281,21 +150,11 @@ export async function setPathway(data: Pathway, setCourse = true) { } if (setCourse) { - const orderedCourses = (data.courses || []).sort( - (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() - ); - // .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); - // .map((lesson) => ({ - // ...lesson, - // profile: lesson.profile && tutorsById[lesson.profile.id], - // })); - courses.set(orderedCourses); + courses.set(data.pathway_course || []); } - delete data.courses; - - if (data.metadata && !Object.values(data.metadata)) { - data.metadata = { + if (data.landingpage && !Object.values(data.landingpage)) { + data.landingpage = { requirements: '', description: '', goals: '', @@ -321,4 +180,4 @@ export async function setPathway(data: Pathway, setCourse = true) { data.certificate_theme = 'professional'; } pathway.set(data); -} +} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/functions/pathway.ts b/apps/dashboard/src/lib/utils/functions/pathway.ts new file mode 100644 index 000000000..f6f667e94 --- /dev/null +++ b/apps/dashboard/src/lib/utils/functions/pathway.ts @@ -0,0 +1,31 @@ +import type { Pathway } from '../types'; + +export const isPathwayFree = (cost: number) => !(Number(cost) > 0); + +export const getStudentInviteLink = (_pathway: Pathway, orgSiteName: string, origin: string) => { + const hash = encodeURIComponent( + btoa( + JSON.stringify({ + id: _pathway.id, + name: _pathway.title, + description: _pathway.description, + orgSiteName + }) + ) + ); + + return `${origin}/invite/s/${hash}`; +}; + +const tagsToReplace: { [k: string]: string } = { + '&': '&', + '<': '<', + '>': '>' +}; + +export function replaceHTMLTag(text: string) { + return text + .split('') + .map((char) => tagsToReplace[char] || char) + .join(''); +} diff --git a/apps/dashboard/src/lib/utils/services/courses/index.ts b/apps/dashboard/src/lib/utils/services/courses/index.ts index 16260a17e..00a7dccfe 100644 --- a/apps/dashboard/src/lib/utils/services/courses/index.ts +++ b/apps/dashboard/src/lib/utils/services/courses/index.ts @@ -219,33 +219,6 @@ export async function updateCourse( return course.logo; } -export async function updatePathway( - // courseId: Course['id'], - // avatar: string | undefined, - // course: Partial -) { - // if (avatar && courseId) { - // const filename = `course/${courseId + Date.now()}.webp`; - - // const { data } = await supabase.storage.from('avatars').upload(filename, avatar, { - // cacheControl: '3600', - // upsert: false - // }); - - // if (data) { - // const { data: response } = supabase.storage.from('avatars').getPublicUrl(filename); - - // if (!response.publicUrl) return; - - // course.logo = response.publicUrl; - // } - // } - - // await supabase.from('course').update(course).match({ id: courseId }); - - // return course.logo; -} - export async function deleteCourse(courseId: Course['id']) { return await supabase.from('course').update({ status: 'DELETED' }).match({ id: courseId }); } diff --git a/apps/dashboard/src/lib/utils/services/notification/notification.ts b/apps/dashboard/src/lib/utils/services/notification/notification.ts index 74b63c84d..1bad7b344 100644 --- a/apps/dashboard/src/lib/utils/services/notification/notification.ts +++ b/apps/dashboard/src/lib/utils/services/notification/notification.ts @@ -5,6 +5,7 @@ export const NOTIFICATION_NAME = { VERIFY_EMAIL: 'VERFIY_EMAIL', INVITE_TEACHER: 'INVITE TEACHER', WELCOME_TEACHER_TO_COURSE: 'WELCOME TEACHER TO COURSE', + WELCOME_TEACHER_TO_PATHWAY: 'WELCOME TEACHER TO PATHWAY', SEND_TEACHER_STUDENT_BUY_REQUEST: 'SEND TEACHER STUDENT BUY REQUEST', STUDENT_PROOVE_COURSE_PAYMENT: 'STUDENT PROOVE COURSE PAYMENT', STUDENT_COURSE_WELCOME: 'STUDENT COURSE WELCOME', @@ -19,6 +20,7 @@ const NAME_TO_PATH = { [NOTIFICATION_NAME.VERIFY_EMAIL]: '/api/email/verify_email', [NOTIFICATION_NAME.INVITE_TEACHER]: '/api/email/invite', [NOTIFICATION_NAME.WELCOME_TEACHER_TO_COURSE]: '/api/email/course/teacher_welcome', + [NOTIFICATION_NAME.WELCOME_TEACHER_TO_PATHWAY]: '/api/email/pathway/teacher_welcome', [NOTIFICATION_NAME.SEND_TEACHER_STUDENT_BUY_REQUEST]: '/api/email/course/teacher_student_buycourse', [NOTIFICATION_NAME.STUDENT_PROOVE_COURSE_PAYMENT]: '/api/email/course/student_prove_payment', diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index cc9468c0f..30b264f94 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -1,6 +1,6 @@ import { supabase } from '$lib/utils/functions/supabase'; -import type { Pathway, PathwayCourse } from '$lib/utils/types'; -import type { PostgrestSingleResponse } from '@supabase/supabase-js'; +import type { Pathway, PathwayCourse, ProfilePathwayProgress } from '$lib/utils/types'; +import type { PostgrestError, PostgrestSingleResponse } from '@supabase/supabase-js'; export function addPathwayGroupMember(member: any) { return supabase.from('groupmember').insert(member).select(); @@ -42,6 +42,11 @@ const ID_QUERY = ` currency, landingpage, certificate_theme, + group(*, + members:groupmember(*, + profile(*) + ) + ), pathway_course ( id, order, @@ -111,4 +116,21 @@ export function updatePathwayCourses(id: any, pathwayId: Pathway['id'], courseId .from('pathway_course') .update({ order: order }) .match({ id: id, pathway_id: pathwayId, course_id: courseId }); +} + +export async function fetchProfilePathwayProgress( + pathwayId, + profileId +): Promise<{ + data: ProfilePathwayProgress[] | null; + error: PostgrestError | null; +}> { + const { data, error } = await supabase + .rpc('get_pathway_progress', { + pathway_id_arg: pathwayId, + profile_id_arg: profileId + }) + .returns(); + + return { data, error }; } \ No newline at end of file diff --git a/apps/dashboard/src/routes/api/email/pathway/+server.js b/apps/dashboard/src/routes/api/email/pathway/+server.js new file mode 100644 index 000000000..d70c3f59d --- /dev/null +++ b/apps/dashboard/src/routes/api/email/pathway/+server.js @@ -0,0 +1,53 @@ +import { json } from '@sveltejs/kit'; +import { getSupabase } from '$lib/utils/functions/supabase'; +import sendEmail from '$mail/sendEmail'; + +const supabase = getSupabase(); + +export async function POST({ request }) { + const { to, name, orgName, pathwayName, orgSiteName } = await request.json(); + const accessToken = request.headers.get('Authorization') || ''; + console.log('/POST api/email/pathway/teacher_welcome', to, name, orgName); + + if (!to || !name || !orgName || !pathwayName || !orgSiteName) { + return json({ success: false, message: 'Missing required fields' }, { status: 400 }); + } + + let user; + try { + const { data } = await supabase.auth.getUser(accessToken); + user = data.user; + } catch (error) { + console.error(error); + } + + if (!user) { + return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); + } + + const origin = request.headers.get('origin'); + const inviteLink = `${origin}/org/${orgSiteName}/pathways`; + + const emailData = [ + { + from: `"${orgName} (via ClassroomIO.com)" `, + to, + subject: `You have been invited to a pathway in ${orgName}!`, + content: ` +

Hey ${name},

+

You have been given access to teach a pathway by ${orgName}

+

The pathway is titled: ${pathwayName}

+ + ` + } + ]; + + await sendEmail(emailData); + + return json({ + success: true, + message: 'Email sent' + }); +} diff --git a/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte index 7313fa61f..2f55ff37a 100644 --- a/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/people/+page.svelte @@ -19,7 +19,7 @@ import { group } from '$lib/components/Pathways/store'; import { snackbar } from '$lib/components/Snackbar/store'; import { ROLE_LABEL, ROLES } from '$lib/utils/constants/roles'; - import { deleteMemberModal } from '$lib/components/Course/components/People/store'; + import { deleteMemberModal } from '$lib/components/Pathways/components/People/store'; import { deleteGroupMember, updatedGroupMember } from '$lib/utils/services/courses'; import PageNav from '$lib/components/PageNav/index.svelte'; @@ -34,9 +34,9 @@ import ComingSoon from '$lib/components/ComingSoon/index.svelte'; import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte'; import PathwayContainer from '$lib/components/Pathways/components/PathwayContainer.svelte'; - import InvitationModal from '$lib/components/Course/components/People/InvitationModal.svelte'; - import DeleteConfirmation from '$lib/components/Course/components/People/DeleteConfirmation.svelte'; - import type { Person, ProfileRole } from '$lib/components/Course/components/People/types'; + import InvitationModal from '$lib/components/Pathways/components/People/InvitationModal.svelte'; + import DeleteConfirmation from '$lib/components/Pathways/components/People/DeleteConfirmation.svelte'; + import type { Person, ProfileRole } from '$lib/components/Pathways/components/People/types'; export let data; From ef83c9baa3d1cf3ded3a7f83ff3aa8cad5ea001d Mon Sep 17 00:00:00 2001 From: tunny17 Date: Wed, 21 Aug 2024 09:45:46 +0100 Subject: [PATCH 06/22] feat: course landing page integration --- .../components/Editor/HeaderForm.svelte | 2 +- .../components/Editor/InstructorForm.svelte | 28 +++---- .../components/Editor/PricingForm.svelte | 23 +++--- .../components/Editor/ReviewsForm.svelte | 4 +- .../components/Editor/index.svelte | 35 +++++---- .../PathwayLandingPage/index.svelte | 20 ++--- .../src/lib/components/Pathways/store.ts | 74 ++++--------------- .../src/lib/utils/services/pathways/index.ts | 20 +++++ apps/dashboard/src/lib/utils/types/index.ts | 5 ++ .../src/routes/pathways/[id]/+page.svelte | 4 +- .../pathways/[id]/landingpage/+page.svelte | 4 +- 11 files changed, 104 insertions(+), 115 deletions(-) diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte index 4cf467ea7..3dee55655 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte @@ -36,7 +36,7 @@ placeholder="www.youtube.com/watch?v=uYRq60G5XTk" helperMessage={$t('course.navItem.landing_page.editor.header_form.helper')} type="text" - bind:value={pathway.metadata.videoUrl} + bind:value={pathway.landingpage.videoUrl} />

{$t('course.navItem.landing_page.editor.header_form.replace_cover')}

diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/InstructorForm.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/InstructorForm.svelte index f15a1e23f..64c254f93 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/InstructorForm.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/InstructorForm.svelte @@ -3,12 +3,14 @@ import set from 'lodash/set'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; + + import type { Pathway } from '$lib/utils/types'; + import { t } from '$lib/utils/functions/translations'; + import { uploadAvatar } from '$lib/utils/services/pathways'; + import TextArea from '$lib/components/Form/TextArea.svelte'; import TextField from '$lib/components/Form/TextField.svelte'; import UploadImage from '$lib/components/UploadImage/index.svelte'; - import { uploadAvatar } from '$lib/utils/services/courses'; - import type { Pathway } from '$lib/utils/types'; - import { t } from '$lib/utils/functions/translations'; export let pathway: Pathway; let name: string | undefined; @@ -42,18 +44,18 @@ if (isEmpty(pathway) || hasBeenSet) return; hasBeenSet = true; - name = get(pathway, 'metadata.instructor.name'); - role = get(pathway, 'metadata.instructor.role'); - imgUrl = get(pathway, 'metadata.instructor.imgUrl'); - description = get(pathway, 'metadata.instructor.description'); - pathwayNo = get(pathway, 'metadata.instructor.coursesNo'); + name = get(pathway, 'landingpage.instructor.name'); + role = get(pathway, 'landingpage.instructor.role'); + imgUrl = get(pathway, 'landingpage.instructor.imgUrl'); + description = get(pathway, 'landingpage.instructor.description'); + pathwayNo = get(pathway, 'landingpage.instructor.coursesNo'); } - $: setter(name, 'metadata.instructor.name'); - $: setter(role, 'metadata.instructor.role'); - $: setter(imgUrl, 'metadata.instructor.imgUrl'); - $: setter(description, 'metadata.instructor.description'); - $: setter(pathwayNo, 'metadata.instructor.coursesNo'); + $: setter(name, 'landingpage.instructor.name'); + $: setter(role, 'landingpage.instructor.role'); + $: setter(imgUrl, 'landingpage.instructor.imgUrl'); + $: setter(description, 'landingpage.instructor.description'); + $: setter(pathwayNo, 'landingpage.instructor.coursesNo'); $: onAvatarChange(avatar); diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/PricingForm.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/PricingForm.svelte index 2376b103d..0a74e4ddb 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/PricingForm.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/PricingForm.svelte @@ -25,10 +25,10 @@ if (isEmpty(pathway) || hasBeenSet) return; hasBeenSet = true; - paymentLink = get(pathway, 'metadata.paymentLink', ''); - discount = get(pathway, 'metadata.discount', 0); - showDiscount = get(pathway, 'metadata.showDiscount', false); - giftToggled = get(pathway, 'metadata.reward.show', false); + paymentLink = get(pathway, 'landingpage.paymentLink', ''); + discount = get(pathway, 'landingpage.discount', 0); + showDiscount = get(pathway, 'landingpage.showDiscount', false); + giftToggled = get(pathway, 'landingpage.reward.show', false); } function setter(value: string | number | boolean | undefined, setterKey: string) { @@ -41,14 +41,14 @@ function handleChange(html: string) { const _pathway = cloneDeep(pathway); - set(_pathway, 'metadata.reward.description', html); + set(_pathway, 'landingpage.reward.description', html); pathway = _pathway; } - $: setter(showDiscount, 'metadata.showDiscount'); - $: setter(paymentLink, 'metadata.paymentLink'); - $: setter(discount, 'metadata.discount'); - $: setter(giftToggled, 'metadata.reward.show'); + $: setter(showDiscount, 'landingpage.showDiscount'); + $: setter(paymentLink, 'landingpage.paymentLink'); + $: setter(discount, 'landingpage.discount'); + $: setter(giftToggled, 'landingpage.reward.show'); $: setDefaults(pathway); @@ -116,7 +116,10 @@

- +
{/if} {/if} diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/ReviewsForm.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/ReviewsForm.svelte index 249146c45..16997e56c 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/ReviewsForm.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/ReviewsForm.svelte @@ -18,7 +18,7 @@ export let pathway = {}; - let reviews = get(pathway, 'metadata.reviews', []); + let reviews = get(pathway, 'landingpage.reviews', []); let reviewToExpand = null; let errors = {}; @@ -114,7 +114,7 @@ reviewToExpand = id; } - $: setter(reviews, 'metadata.reviews'); + $: setter(reviews, 'landingpage.reviews'); diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/index.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/index.svelte index 36629167e..4eb77312e 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/index.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/index.svelte @@ -24,6 +24,7 @@ import CustomPromptBtn from '$lib/components/AI/AIButton/CustomPromptBtn.svelte'; import type { Pathway } from '$lib/utils/types'; import { t } from '$lib/utils/functions/translations'; + import { updatePathway } from '$lib/utils/services/pathways/courses'; export let pathway: Pathway; export let pathwayId: string; @@ -49,31 +50,31 @@ }, { key: 2, - path: 'metadata.about', + path: 'landingpage.about', title: 'About', enableAIWriter: true, initPrompt: 'Please write a detailed course requirement for this course:' }, { key: 3, - path: 'metadata.objectives', + path: 'landingpage.objectives', title: 'Objectives', enableAIWriter: true, initPrompt: 'Please write educational objectives for this course:' }, { key: 4, - path: 'metadata.reviews', + path: 'landingpage.reviews', title: $t('course.navItem.landing_page.editor.title.reviews') }, { key: 5, - path: 'metadata.instructor', + path: 'landingpage.instructor', title: $t('course.navItem.landing_page.editor.title.instructor') }, { key: 6, - path: 'metadata.pricing', + path: 'landingpage.pricing', title: $t('course.navItem.landing_page.editor.title.pricing') } ]; @@ -109,17 +110,15 @@ loading = true; pathway.slug = pathway.slug || generateSlug(pathway.title); - // await updateCourse(pathwayId, undefined, { - // ...pathway, - // attendance: undefined, - // group: undefined, - // lessons: undefined, - // polls: undefined, - // slug: pathway.slug - // }); - - // loading = false; - // syncCourseStore(pathway); + await updatePathway(pathwayId, undefined, { + ...pathway, + group: undefined, + pathway_course: undefined, + slug: pathway.slug + }); + + loading = false; + syncPathwayStore(pathway); } async function handlePreview() { @@ -219,9 +218,9 @@ {#if selectedSection.key === 1} {:else if selectedSection.key === 2} - + {:else if selectedSection.key === 3} - + {:else if selectedSection.key === 4} {:else if selectedSection.key === 5} diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte index 0e901835a..38e402535 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte @@ -83,13 +83,13 @@ observer?.destroy(); }); - $: video = get(pathwayData, 'metadata.videoUrl'); - $: allowNewStudent = get(pathwayData, 'metadata.allowNewStudent'); + $: video = get(pathwayData, 'landingpage.videoUrl'); + $: allowNewStudent = get(pathwayData, 'landingpage.allowNewStudent'); $: bannerImage = get(pathwayData, 'logo'); - $: instructor = get(pathwayData, 'metadata.instructor') || {}; + $: instructor = get(pathwayData, 'landingpage.instructor') || {}; $: initPlyr(player, video); $: { - reviews = get(pathwayData, 'metadata.reviews') || []; + reviews = get(pathwayData, 'landingpage.reviews') || []; totalRatings = reviews?.reduce((acc = 0, review) => acc + (review?.rating || 0), 0); averageRating = totalRatings / reviews?.length; } @@ -265,7 +265,7 @@
    - +
@@ -278,7 +278,7 @@
@@ -360,10 +360,10 @@ -
+
{#if reviews && reviews.length > 0} -
+

{$t('course.navItem.landing_page.reviews')}

@@ -374,7 +374,7 @@ {#each reviews.slice(0, 4) as review, id} {#if !review.hide} -
+

{$t('course.navItem.landing_page.instructor')} diff --git a/apps/dashboard/src/lib/components/Pathways/store.ts b/apps/dashboard/src/lib/components/Pathways/store.ts index c60ef5f79..58a2c723d 100644 --- a/apps/dashboard/src/lib/components/Pathways/store.ts +++ b/apps/dashboard/src/lib/components/Pathways/store.ts @@ -36,8 +36,7 @@ export const defaultPathway: Pathway = { id: '', slug: '', title: '', - description: - '', + description: '', prerequisite: '', is_published: false, lms_certificate: false, @@ -50,66 +49,25 @@ export const defaultPathway: Pathway = { enrollment_date: '', landingpage: { header: { - title: 'Online Business Masterclass: Sell Your Own Digital Products', - description: - 'Create, Launch & Market Your Own Online Business w/ Digital Products such as Online Courses, Coaching, eBooks, Webinars+', - duration: 'Estimated 8 weeks', - cost: 300, - buttonLabel: 'Enrol' + title: '', + description: '', + duration: '', + cost: 0, + buttonLabel: 'Enroll' }, - about: - "Become a Professional UX Specialist course offers a comprehensive pathway to mastery in user experience design. In today's digital landscape, exceptional user experiences are paramount for success. This course equips you with the skills and knowledge needed to create intuitive, engaging, and impactful designs that resonate with users. Gain a competitive edge, unlock career opportunities, and make a meaningful impact in shaping the future of digital interactions.", - objectives: - '
  • Develop a comprehensive understanding of user experience (UX) design principles, methodologies, and best practices.
  • Acquire hands-on experience in conducting user research, including methods for gathering, analyzing, and interpreting user insights.
  • Cultivate proficiency in creating wireframes, prototypes, and interactive designs using industry-standard tools and techniques.
  • Master the art of usability testing and iterative design processes to refine and optimize user interfaces for enhanced usability and user satisfaction.
  • Prepare for a successful career as a UX specialist by building a strong portfolio of diverse projects showcasing practical application of UX design principles and methodologies.
  • ', - reviews: [ - { - id: 1, - hide: false, - name: 'Nana Ama Agyekumwaa', - avatar_url: 'https://i.pravatar.cc/150?img=63', - rating: 1, - created_at: new Date().toISOString(), - description: 'Great and interesting, tutors breaks things down easily and illustrates ...' - }, - { - id: 2, - hide: false, - name: 'Nana Ama Agyekumwaa', - avatar_url: 'https://i.pravatar.cc/150?img=64', - rating: 1, - created_at: new Date().toISOString(), - description: 'Great and interesting, tutors breaks things down easily and illustrates ...' - }, - { - id: 3, - hide: false, - name: 'Nana Ama Agyekumwaa', - avatar_url: 'https://i.pravatar.cc/150?img=65', - rating: 1, - created_at: new Date().toISOString(), - description: 'Great and interesting, tutors breaks things down easily and illustrates ...' - }, - { - id: 4, - hide: false, - name: 'Nana Ama Agyekumwaa', - avatar_url: 'https://i.pravatar.cc/150?img=66', - rating: 1, - created_at: new Date().toISOString(), - description: 'Great and interesting, tutors breaks things down easily and illustrates ...' - } - ], + about: '', + objectives: ``, + reviews: [], instructor: { - name: 'Marvellous Woods', - role: 'Product Designer', - coursesNo: 50, - description: - "Derrick Amenuve is UX/Product Designer who solves human needs by creating usable and aesthetically pleasing interfaces to enhance human experiences and achieve business success. With focus on research and data driven approaches to create end to end designs that is true to the product's goals and the visual experience.", - imgUrl: 'https://avatars.githubusercontent.com/u/74567285?v=4' + name: '', + role: '', + coursesNo: 0, + description: '', + imgUrl: '' }, allowNewStudent: true, showDiscount: true, - discount: 50 + discount: 0 }, is_certificate_downloadable: false, certificate_theme: 'professional', @@ -180,4 +138,4 @@ export async function setPathway(data: Pathway, setCourse = true) { data.certificate_theme = 'professional'; } pathway.set(data); -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 30b264f94..0c83deea8 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -133,4 +133,24 @@ export async function fetchProfilePathwayProgress( .returns(); return { data, error }; +} + +export async function uploadAvatar(pathwayId: string, avatar: string) { + const filename = `pathway/${pathwayId + Date.now()}.webp`; + let logo; + + const { data } = await supabase.storage.from('avatars').upload(filename, avatar, { + cacheControl: '3600', + upsert: false + }); + + if (data) { + const { data } = supabase.storage.from('avatars').getPublicUrl(filename); + + if (!data.publicUrl) return; + + logo = data.publicUrl; + } + + return logo; } \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/types/index.ts b/apps/dashboard/src/lib/utils/types/index.ts index 416a077ae..3efcd631e 100644 --- a/apps/dashboard/src/lib/utils/types/index.ts +++ b/apps/dashboard/src/lib/utils/types/index.ts @@ -134,6 +134,10 @@ export interface PathwayMetadata { about: string; objectives: string; reviews?: Array; + goals: string; + requirements: string; + description: string; + videoUrl: string; instructor?: { name: string; role: string; @@ -147,6 +151,7 @@ export interface PathwayMetadata { show: boolean; description: string; }; + discount: number; } export interface LessonCommentInsertPayload { diff --git a/apps/dashboard/src/routes/pathways/[id]/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/+page.svelte index fcf949972..07e1459f8 100644 --- a/apps/dashboard/src/routes/pathways/[id]/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/+page.svelte @@ -99,8 +99,10 @@ [reactionType]: reactedAuthorIds }; + console.log('reactedFeed', reactedFeed); + const response = await supabase - .from('course_newsfeed') + .from('pathway_newsfeed') .update({ reaction: reactedFeed.reaction }) diff --git a/apps/dashboard/src/routes/pathways/[id]/landingpage/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/landingpage/+page.svelte index d235117ee..7d2ab07a5 100644 --- a/apps/dashboard/src/routes/pathways/[id]/landingpage/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/landingpage/+page.svelte @@ -14,8 +14,8 @@ let pathwayData: Pathway = $pathway; - function setPathwayData(pathway: Pathway, courses: PathwayCourse[]) { - pathwayData = { ...pathway, courses }; + function setPathwayData(pathway: Pathway, pathway_course: PathwayCourse[]) { + pathwayData = { ...pathway, pathway_course }; } function syncPathwayStore(_pathwayData: Pathway) { From 2a942bfa5ca3a5eb3e82795a17f8a35487888713 Mon Sep 17 00:00:00 2001 From: emmanuel Date: Wed, 21 Aug 2024 12:44:51 +0100 Subject: [PATCH 07/22] working on the LMS side --- .../components/LMS/components/Learning.svelte | 8 +- .../src/lib/components/Org/Pathway/api.ts | 87 ++++++++++++++++++- apps/dashboard/src/routes/lms/+page.svelte | 4 +- .../pathways/[id]/settings/+page.svelte | 2 +- 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/lib/components/LMS/components/Learning.svelte b/apps/dashboard/src/lib/components/LMS/components/Learning.svelte index 166e48449..9cab12c02 100644 --- a/apps/dashboard/src/lib/components/LMS/components/Learning.svelte +++ b/apps/dashboard/src/lib/components/LMS/components/Learning.svelte @@ -11,12 +11,12 @@ let open = false; const getCompletedCoursesCount = (pathway) => { - return pathway.courses?.filter((course) => course.progress_rate === 100).length || 0; + return pathway.pathway_course?.filter((course) => course.progress_rate === 100).length || 0; }; // this return the first course it can find in the array that is been taken but not yet completed. const getCurrentCourse = (pathway) => { - return pathway.courses?.find( + return pathway.pathway_course?.find( (course) => course.is_unlocked === true && course.progress_rate !== 100 ); }; @@ -54,7 +54,7 @@ > {getCompletedCoursesCount(course) || 0} {$t('lms_pathway.of')} - {course.courses.length} + {course?.total_course} {$t('lms_pathway.completed')} @@ -62,7 +62,7 @@ pathway.id); + + const { data: allCourses, error: courseError } = await supabase + .from('pathway_course') + .select( + ` + id, + pathway_id, + order, + course_id, + course ( + id, + title, + logo, + description, + banner_image, + created_at, + lesson ( + is_complete + ), + group_id ( + groupmember ( + id + ) + ) + ) + ` + ) + .in('pathway_id', pathwayIds); + + if (courseError || !Array.isArray(allCourses)) { + console.error('Error fetching courses:', courseError); + return { + allPathways + }; + } + + // Step 3: Attach courses to their respective pathways and rename pathway_course to courses + const pathwaysWithCourses = allPathways.map((pathway: any) => { + const courses = allCourses.filter((course: any) => course.pathway_id === pathway.id); + return { + ...pathway, + pathway_course: courses // Rename pathway_course to courses + }; + }); + + return { allPathways: pathwaysWithCourses }; } diff --git a/apps/dashboard/src/routes/lms/+page.svelte b/apps/dashboard/src/routes/lms/+page.svelte index ed127be38..ae5f6ea7b 100644 --- a/apps/dashboard/src/routes/lms/+page.svelte +++ b/apps/dashboard/src/routes/lms/+page.svelte @@ -11,7 +11,7 @@ import { t } from '$lib/utils/functions/translations'; import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte'; import { lmsCourses } from '$lib/components/LMS/store'; - import type { LmsCourse } from '$lib/components/LMS/store'; + import type { LMSCourse } from '$lib/components/LMS/store'; import { fetchPathways } from '$lib/components/Org/Pathway/api'; let hasFetched = false; @@ -56,7 +56,7 @@ } } - function calcTotalProgress(courses: LmsCourse[] | any) { + function calcTotalProgress(courses: LMSCourse[] | any) { totalCompleted = courses.reduce((acc, cur) => acc + (cur.progress_rate || 0), 0); totalLessons = courses.reduce((acc, cur) => acc + (cur.total_lessons || 0), 0); progressPercentage = Math.round((totalCompleted / totalLessons) * 100) || 0; diff --git a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte index e0d0c3ff3..1ed83ad5b 100644 --- a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte @@ -120,7 +120,7 @@ logo: pathway.logo || '', is_published: !!pathway.is_published, prerequisite: pathway.prerequisite, - metadata: pathway.metadata, + metadata: pathway.landingpage, lms_certificate: pathway.lms_certificate, courses_certificate: pathway.courses_certificate }; From 04c4715d3c83332121c3fd8f441d119f8a143ba1 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Wed, 21 Aug 2024 20:35:51 +0100 Subject: [PATCH 08/22] feat: bug --- .../components/CourseLandingPage/index.svelte | 8 ++--- .../components/Certificate/Design.svelte | 2 +- .../components/Editor/HeaderForm.svelte | 1 + .../components/Editor/index.svelte | 2 +- .../PathwayLandingPage/index.svelte | 1 + .../src/lib/components/Pathways/store.ts | 1 + .../lib/components/UploadWidget/index.svelte | 1 + .../utils/services/pathways/courses/index.ts | 29 ---------------- .../src/lib/utils/services/pathways/index.ts | 33 ++++++++++++++++++- apps/dashboard/src/lib/utils/types/index.ts | 19 +++-------- .../pathways/[id]/settings/+page.svelte | 17 +++++----- 11 files changed, 55 insertions(+), 59 deletions(-) delete mode 100644 apps/dashboard/src/lib/utils/services/pathways/courses/index.ts diff --git a/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte b/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte index b312ac1ef..3e5889cc4 100644 --- a/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte +++ b/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte @@ -269,7 +269,7 @@
    {#each lessons as lesson, index} -
    +
    - - -
    - {/each} +
    + {/each}

    diff --git a/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte b/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte index 669c30276..1f62cd6b4 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/Certificate/Design.svelte @@ -7,7 +7,7 @@ import { globalStore } from '$lib/utils/store/app'; import { t } from '$lib/utils/functions/translations'; import { currentOrg, isFreePlan } from '$lib/utils/store/org'; - import { updatePathway } from '$lib/utils/services/pathways/courses'; + import { updatePathway } from '$lib/utils/services/pathways'; import { saveCertificateValidation } from '$lib/utils/functions/validator'; import { snackbar } from '$lib/components/Snackbar/store'; diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte index 3dee55655..03ade396b 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/components/Editor/HeaderForm.svelte @@ -38,6 +38,7 @@ type="text" bind:value={pathway.landingpage.videoUrl} /> +

    {$t('course.navItem.landing_page.editor.header_form.replace_cover')}

    {#if !editMode} diff --git a/apps/dashboard/src/lib/components/Pathways/store.ts b/apps/dashboard/src/lib/components/Pathways/store.ts index 58a2c723d..5ee9e57c8 100644 --- a/apps/dashboard/src/lib/components/Pathways/store.ts +++ b/apps/dashboard/src/lib/components/Pathways/store.ts @@ -35,6 +35,7 @@ export const group = writable({ export const defaultPathway: Pathway = { id: '', slug: '', + logo: '', title: '', description: '', prerequisite: '', diff --git a/apps/dashboard/src/lib/components/UploadWidget/index.svelte b/apps/dashboard/src/lib/components/UploadWidget/index.svelte index a6e33a1e0..a0ba622a3 100644 --- a/apps/dashboard/src/lib/components/UploadWidget/index.svelte +++ b/apps/dashboard/src/lib/components/UploadWidget/index.svelte @@ -95,6 +95,7 @@ } onMount(handleSubmit); + $: console.log('sup', $handleOpenWidget.open); -) { - if (avatar && pathwayId) { - const filename = `pathway/${pathwayId + Date.now()}.webp`; - - const { data } = await supabase.storage.from('avatars').upload(filename, avatar, { - cacheControl: '3600', - upsert: false - }); - - if (data) { - const { data: response } = supabase.storage.from('avatars').getPublicUrl(filename); - - if (!response.publicUrl) return; - - pathway.logo = response.publicUrl; - } - } - - await supabase.from('pathway').update(pathway).match({ id: pathwayId }); - - return pathway.logo; -} \ No newline at end of file diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 0c83deea8..247fbe511 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -60,7 +60,7 @@ const ID_QUERY = ` banner_image, created_at, lesson ( - is_complete + lesson_completion(id, profile_id, is_complete) ), group_id ( groupmember ( @@ -102,6 +102,37 @@ export async function fetchPathway(pathwayId?: Pathway['id'], slug?: Pathway['sl }; } +export async function updatePathway ( + pathwayId: Pathway['id'], + avatar: string | undefined, + pathway: Partial +) { + if (avatar && pathwayId) { + const filename = `pathway/${pathwayId + Date.now()}.webp`; + + const { data } = await supabase.storage.from('avatars').upload(filename, avatar, { + cacheControl: '3600', + upsert: false + }); + + if (data) { + const { data: response } = supabase.storage.from('avatars').getPublicUrl(filename); + + if (!response.publicUrl) return; + + pathway.logo = response.publicUrl; + } + } + + await supabase.from('pathway').update(pathway).match({ id: pathwayId }); + + return pathway.logo; +} + +export async function deletePathway(pathwayId: Pathway['id']) { + return await supabase.from('pathway').update({ status: 'DELETED' }).match({ id: pathwayId }); +} + export function addPathwayCourse(pathwayId: Pathway['id'], courseId: PathwayCourse['course_id']) { return supabase.from('pathway_course').insert([ { diff --git a/apps/dashboard/src/lib/utils/types/index.ts b/apps/dashboard/src/lib/utils/types/index.ts index 3efcd631e..a501ca018 100644 --- a/apps/dashboard/src/lib/utils/types/index.ts +++ b/apps/dashboard/src/lib/utils/types/index.ts @@ -279,27 +279,18 @@ export interface Course { polls: { status: string }[]; } -export interface CourseCompletion { - id?: number; - course_id: string; - profile_id: string; - is_complete: boolean; - created_at: string; - updated_at: string; -} - export interface PathwayCourse { id: string; course: Course; course_id: any; pathway_id: any; order: number; - created_at: string; - updated_at: string; - estimated_hours: number; + // created_at: string; + // updated_at: string; + // estimated_hours: number; is_unlocked: boolean; - is_completed: CourseCompletion[]; - is_published: boolean; + // course_completion: CourseCompletion[]; + // is_published: boolean; } export interface Pathway { diff --git a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte index e0d0c3ff3..35794b963 100644 --- a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte @@ -12,10 +12,15 @@ Loading } from 'carbon-components-svelte'; import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte'; + + import type { Pathway } from '$lib/utils/types'; import { t } from '$lib/utils/functions/translations'; - import { pathway, pathwaySettings, RADIO_VALUE } from '$lib/components/Pathways/store'; - import { deletePathway, updatePathways } from '$lib/utils/services/pathways'; + import { snackbar } from '$lib/components/Snackbar/store'; + import generateSlug from '$lib/utils/functions/generateSlug'; + import { handleOpenWidget } from '$lib/components/CourseLandingPage/store'; + import { deletePathway, updatePathway } from '$lib/utils/services/pathways'; import { currentOrgDomain, currentOrg, currentOrgPath } from '$lib/utils/store/org'; + import { pathway, pathwaySettings, RADIO_VALUE } from '$lib/components/Pathways/store'; import PageNav from '$lib/components/PageNav/index.svelte'; import PageBody from '$lib/components/PageBody/index.svelte'; @@ -25,12 +30,8 @@ import { VARIANTS } from '$lib/components/PrimaryButton/constants'; import UploadWidget from '$lib/components/UploadWidget/index.svelte'; import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; - import { handleOpenWidget } from '$lib/components/CourseLandingPage/store'; - import PathwayContainer from '$lib/components/Pathways/components/PathwayContainer.svelte'; import SectionTitle from '$lib/components/Org/SectionTitle.svelte'; - import generateSlug from '$lib/utils/functions/generateSlug'; - import type { Pathway } from '$lib/utils/types'; - import { snackbar } from '$lib/components/Snackbar/store'; + import PathwayContainer from '$lib/components/Pathways/components/PathwayContainer.svelte'; let isSaving = false; let isDeleting = false; @@ -74,7 +75,7 @@ courses_certificate } = $pathwaySettings; - await updatePathways($pathway.id, avatar, { + await updatePathway($pathway.id, avatar, { title, logo, description, From 361b8108bece3e1fcffa34dfae31620855579369 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Thu, 22 Aug 2024 21:28:12 +0100 Subject: [PATCH 09/22] code clean up --- apps/dashboard/src/lib/components/UploadWidget/index.svelte | 1 - apps/dashboard/src/lib/utils/types/index.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/apps/dashboard/src/lib/components/UploadWidget/index.svelte b/apps/dashboard/src/lib/components/UploadWidget/index.svelte index a0ba622a3..a6e33a1e0 100644 --- a/apps/dashboard/src/lib/components/UploadWidget/index.svelte +++ b/apps/dashboard/src/lib/components/UploadWidget/index.svelte @@ -95,7 +95,6 @@ } onMount(handleSubmit); - $: console.log('sup', $handleOpenWidget.open); Date: Sat, 24 Aug 2024 14:28:05 +0100 Subject: [PATCH 10/22] Lms pathway fully done --- .../Courses/components/Card/index.svelte | 10 ++- .../src/lib/components/Courses/index.svelte | 41 ++++++---- .../LMS/components/CourseListModal.svelte | 26 +++++-- .../components/LMS/components/Learning.svelte | 31 ++++---- .../src/lib/components/Org/Pathway/api.ts | 2 + .../Pathways/components/AddCourseModal.svelte | 44 +++++++---- .../components/DragAndDropModal.svelte | 26 +++---- .../components/NewsFeed/NewFeedModal.svelte | 28 +++---- .../src/lib/utils/functions/pathway.ts | 10 +++ .../src/lib/utils/services/pathways/index.ts | 56 +++++++++++--- .../src/routes/lms/mylearning/+page.svelte | 74 ++++++++++--------- 11 files changed, 226 insertions(+), 122 deletions(-) diff --git a/apps/dashboard/src/lib/components/Courses/components/Card/index.svelte b/apps/dashboard/src/lib/components/Courses/components/Card/index.svelte index 12c08d07f..7a3c3f947 100644 --- a/apps/dashboard/src/lib/components/Courses/components/Card/index.svelte +++ b/apps/dashboard/src/lib/components/Courses/components/Card/index.svelte @@ -33,7 +33,7 @@ export let isLMS = false; export let isLearningPath = false; export let totalCourse = 0; - export let completedCourse = 0; + export let pathwaycompletedCourses = 0; export let isExplore = false; export let progressRate = 45; export let type: COURSE_TYPE; @@ -63,6 +63,10 @@ } function getCourseUrl() { + if (isLMS && isLearningPath) { + return `/pathways/${id}`; + } + return isOnLandingPage || isExplore ? `/course/${slug}` : `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`; @@ -145,7 +149,7 @@ {$t('courses.course_card.error_message')} - {#if isLearningPath && isLMS} + {#if isLMS && isLearningPath} @@ -175,7 +179,7 @@

    {#if isLearningPath && isLMS} - {completedCourse} / {totalCourse} {$t('lms_pathway.course')} + {pathwaycompletedCourses} / {totalCourse} {$t('lms_pathway.course')} {:else} {totalLessons} {$t('courses.course_card.lessons_number')} diff --git a/apps/dashboard/src/lib/components/Courses/index.svelte b/apps/dashboard/src/lib/components/Courses/index.svelte index bf8d29e06..f13e5dbe5 100644 --- a/apps/dashboard/src/lib/components/Courses/index.svelte +++ b/apps/dashboard/src/lib/components/Courses/index.svelte @@ -5,7 +5,7 @@ import CardLoader from '$lib/components/Courses/components/Card/Loader.svelte'; import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte'; import { courseMetaDeta } from '$lib/components/Courses/store'; - import type { Pathway } from '$lib/utils/types'; + import type { Course, Pathway } from '$lib/utils/types'; import { globalStore } from '$lib/utils/store/app'; import { StructuredList, @@ -16,9 +16,10 @@ } from 'carbon-components-svelte'; import { t } from '$lib/utils/functions/translations'; import { isMobile } from '$lib/utils/store/useMobile'; - import type { LmsCourse } from '$lib/components/LMS/store'; + import type { LMSCourse } from '$lib/components/LMS/store'; + import { getPathwayCompletedCoursesLength } from '$lib/utils/functions/pathway'; - export let courses: LmsCourse[] = []; + export let courses: LMSCourse[] = []; export let emptyTitle = $t('courses.course_card.empty_title'); export let emptyDescription = $t('courses.course_card.empty_description'); export let isExplore = false; @@ -28,16 +29,29 @@ if (!progressRate || !totalItem) { return 0; } - return Math.round((progressRate / totalItem) * 100); } - const getCompletedCourses = (course: Pathway) => { - if (!course.isPathway) return; - const completedCourse = - course.courses?.filter((course) => course.progress_rate === 100).length || 0; - return completedCourse; - }; + function calculatePathwayProgress(pathway: Pathway): number { + if (!pathway.isPathway) return 0; + + const totalCourses = pathway.total_course; + if (totalCourses === 0) return 0; + + // Number of courses completed within the pathway + const completedCourses = getPathwayCompletedCoursesLength(pathway); + + return Math.round((completedCourses / totalCourses) * 100); + } + + function calculateCourseAndPathwayProgress(course: LMSCourse): number { + if (course.isPathway) { + return calculatePathwayProgress(course as Pathway); + } else { + // Individual course progress calculation + return calcProgressRate(course.progress_rate, course.total_lessons); + } + } @@ -105,16 +119,13 @@ type={courseData.type} isLearningPath={courseData.isPathway} totalCourse={courseData.total_course} - completedCourse={getCompletedCourses(courseData)} + pathwaycompletedCourses={getPathwayCompletedCoursesLength(courseData)} currency={courseData.currency} totalLessons={courseData.total_lessons} totalStudents={courseData.total_students} isLMS={$globalStore.isOrgSite} {isExplore} - progressRate={calcProgressRate( - courseData.progress_rate, - courseData.isPathway ? courseData.total_course : courseData.total_lessons - )} + progressRate={calculateCourseAndPathwayProgress(courseData)} /> {/key} {/each} diff --git a/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte b/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte index 33e8155d5..07e1303dc 100644 --- a/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte +++ b/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte @@ -13,6 +13,13 @@ const gotoPathway = () => { return goto(`/pathways/${pathway.id}`); }; + + const courseProgress = (lessons) => { + const totalLesson = lessons.length; + const completedLesson = lessons.filter((lesson) => lesson.is_complete).length; + + return (completedLesson / totalLesson) * 100; + };

    - {#each pathway.courses as course} - + {#each pathway.pathway_course as pathway_course} +
    -

    {course.title}

    - {#if !course.is_unlocked} +

    {pathway_course.course.title}

    + {#if pathway_course.is_unlocked == false || pathway_course.course.is_published == false}

    {$t('lms_pathway.locked')}

    @@ -37,14 +49,14 @@

    - {course.progress_rate === 100 + {courseProgress(pathway_course.course.lesson) === 100 ? `${$t('lms_pathway.completed')}` - : `${course.progress_rate}%`} + : `${courseProgress(pathway_course.course.lesson) || 0}%`}

    {/if} diff --git a/apps/dashboard/src/lib/components/LMS/components/Learning.svelte b/apps/dashboard/src/lib/components/LMS/components/Learning.svelte index 9cab12c02..693c381da 100644 --- a/apps/dashboard/src/lib/components/LMS/components/Learning.svelte +++ b/apps/dashboard/src/lib/components/LMS/components/Learning.svelte @@ -7,18 +7,19 @@ import { ChevronDown } from 'carbon-icons-svelte'; import CourseListModal from '$lib/components/LMS/components/CourseListModal.svelte'; import { lmsCourses } from '$lib/components/LMS/store'; + import { getPathwayCompletedCoursesLength } from '$lib/utils/functions/pathway'; let open = false; - const getCompletedCoursesCount = (pathway) => { - return pathway.pathway_course?.filter((course) => course.progress_rate === 100).length || 0; - }; + // const getCompletedCoursesCount = (pathway) => { + // return ( + // pathway.pathway_course?.filter((course) => course.course.progress_rate === 100).length || 0 + // ); + // }; // this return the first course it can find in the array that is been taken but not yet completed. const getCurrentCourse = (pathway) => { - return pathway.pathway_course?.find( - (course) => course.is_unlocked === true && course.progress_rate !== 100 - ); + return pathway.pathway_course?.find((course) => course.is_unlocked === true); }; const gotoCourse = (id: string | undefined) => { @@ -52,45 +53,45 @@ open = !open; }} > - {getCompletedCoursesCount(course) || 0} + {getPathwayCompletedCoursesLength(course) || 0} {$t('lms_pathway.of')} {course?.total_course} {$t('lms_pathway.completed')}
    - +

    - {getCurrentCourse(course)?.title} + {getCurrentCourse(course)?.course.title || 'No title'}

    - {getCurrentCourse(course)?.description} + {getCurrentCourse(course)?.course.description || 'No descrption'}

    gotoCourse(getCurrentCourse(course)?.id)} + onClick={() => gotoCourse(getCurrentCourse(course)?.course.id)} />
    - {#if getCurrentCourse(course)?.progress_rate && getCurrentCourse(course)?.progress_rate > 0} + {#if getCurrentCourse(course)?.course.progress_rate && getCurrentCourse(course)?.course.progress_rate > 0}
    {/if} - {#if course.courses} + {#if course.pathway_course?.length > 0} {/if} {:else} diff --git a/apps/dashboard/src/lib/components/Org/Pathway/api.ts b/apps/dashboard/src/lib/components/Org/Pathway/api.ts index 0f6ea82e0..f43fcf4a2 100644 --- a/apps/dashboard/src/lib/components/Org/Pathway/api.ts +++ b/apps/dashboard/src/lib/components/Org/Pathway/api.ts @@ -71,12 +71,14 @@ export async function fetchPathways(profileId: string | undefined, orgId: string pathway_id, order, course_id, + is_unlocked, course ( id, title, logo, description, banner_image, + is_published, created_at, lesson ( is_complete diff --git a/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte index 1f3219cdf..21da7885a 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/AddCourseModal.svelte @@ -15,7 +15,7 @@ import type { Course, PathwayCourse } from '$lib/utils/types'; import { t } from '$lib/utils/functions/translations'; import { fetchCourses } from '$lib/utils/services/courses'; - import { addPathwayCourse } from '$lib/utils/services/pathways'; + import { addPathwayCourse, updatePathwayCourses } from '$lib/utils/services/pathways'; import Modal from '$lib/components/Modal/index.svelte'; import DragAndDropModal from './DragAndDropModal.svelte'; @@ -64,6 +64,19 @@ $addCourseModal.step = 1; } + const handleSaveDnD = () => { + $courses.forEach(async (course) => { + ``; + try { + await updatePathwayCourses(course.id, course.pathway_id, course.course_id, course.order); + } catch (error) { + console.error('Error updating course order in Supabase:', error); + } + }); + + $addCourseModal.open = false; + }; + function toggleCourseSelection(course: PathwayCourse, checked: boolean) { if (checked) { // avoid duplicating the course @@ -159,18 +172,6 @@ {/each} - -
    - -
    {:else if $addCourseModal.step === 1} {/if} +
    + {#if $addCourseModal.step === 0} + + {:else} +
    + +
    + {/if} +
    diff --git a/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte index 4e9ffc069..e2f05e7a9 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte @@ -31,18 +31,18 @@ dispatch('update', pathwayCourses); } - function handleSave() { - pathwayCourses.forEach(async (course) => { - ``; - try { - await updatePathwayCourses(course.id, course.pathway_id, course.course_id, course.order); - } catch (error) { - console.error('Error updating course order in Supabase:', error); - } - }); + // function handleSave() { + // pathwayCourses.forEach(async (course) => { + // ``; + // try { + // await updatePathwayCourses(course.id, course.pathway_id, course.course_id, course.order); + // } catch (error) { + // console.error('Error updating course order in Supabase:', error); + // } + // }); - $addCourseModal.open = false; - } + // $addCourseModal.open = false; + // }
    @@ -94,6 +94,6 @@ {/each}
    -
    + diff --git a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte index b255fd46c..c12e6c685 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/NewsFeed/NewFeedModal.svelte @@ -125,19 +125,19 @@ {#if errors.newPost}

    {errors.newPost}

    {/if} -
    -
    - - -
    -
    +
    +
    + + +
    +
    diff --git a/apps/dashboard/src/lib/utils/functions/pathway.ts b/apps/dashboard/src/lib/utils/functions/pathway.ts index f6f667e94..27aaf8b34 100644 --- a/apps/dashboard/src/lib/utils/functions/pathway.ts +++ b/apps/dashboard/src/lib/utils/functions/pathway.ts @@ -29,3 +29,13 @@ export function replaceHTMLTag(text: string) { .map((char) => tagsToReplace[char] || char) .join(''); } + +export const getPathwayCompletedCoursesLength = (pathway: Pathway) => { + if (!pathway.isPathway) return; + const completedCourses = pathway.pathway_course.filter((pathwayCourse) => { + const lessons = pathwayCourse.course.lesson; + return lessons.length > 0 && lessons.every((lesson) => lesson.is_complete); + }).length; + console.log('lesson done', completedCourses); + return completedCourses; +}; diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 30b264f94..cc37cf10d 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -6,7 +6,6 @@ export function addPathwayGroupMember(member: any) { return supabase.from('groupmember').insert(member).select(); } - const SLUG_QUERY = ` id, title, @@ -103,15 +102,23 @@ export async function fetchPathway(pathwayId?: Pathway['id'], slug?: Pathway['sl } export function addPathwayCourse(pathwayId: Pathway['id'], courseId: PathwayCourse['course_id']) { - return supabase.from('pathway_course').insert([ - { - pathway_id: pathwayId, - course_id: courseId, - } - ]).select(); + return supabase + .from('pathway_course') + .insert([ + { + pathway_id: pathwayId, + course_id: courseId + } + ]) + .select(); } -export function updatePathwayCourses(id: any, pathwayId: Pathway['id'], courseId: PathwayCourse['course_id'], order: PathwayCourse['order']) { +export function updatePathwayCourses( + id: any, + pathwayId: Pathway['id'], + courseId: PathwayCourse['course_id'], + order: PathwayCourse['order'] +) { return supabase .from('pathway_course') .update({ order: order }) @@ -133,4 +140,35 @@ export async function fetchProfilePathwayProgress( .returns(); return { data, error }; -} \ No newline at end of file +} + +export async function updatePathways( + pathwayId: Pathway['id'], + avatar: string | undefined, + pathway: Partial +) { + if (avatar && pathwayId) { + const filename = `course/${pathwayId + Date.now()}.webp`; + + const { data } = await supabase.storage.from('avatars').upload(filename, avatar, { + cacheControl: '3600', + upsert: false + }); + + if (data) { + const { data: response } = supabase.storage.from('avatars').getPublicUrl(filename); + + if (!response.publicUrl) return; + + pathway.logo = response.publicUrl; + } + } + + await supabase.from('pathway').update(pathway).match({ id: pathwayId }); + + return pathway.logo; +} + +export async function deletePathway(pathwayId: Pathway['id']) { + return await supabase.from('pathway').update({ status: 'DELETED' }).match({ id: pathwayId }); +} diff --git a/apps/dashboard/src/routes/lms/mylearning/+page.svelte b/apps/dashboard/src/routes/lms/mylearning/+page.svelte index 7236612d7..6ade12bf9 100644 --- a/apps/dashboard/src/routes/lms/mylearning/+page.svelte +++ b/apps/dashboard/src/routes/lms/mylearning/+page.svelte @@ -9,17 +9,22 @@ import { browser } from '$app/environment'; import { t } from '$lib/utils/functions/translations'; import { lmsCourses } from '$lib/components/LMS/store'; - import type { LmsCourse } from '$lib/components/LMS/store'; + import type { LMSCourse } from '$lib/components/LMS/store'; import { fetchPathways } from '$lib/components/Org/Pathway/api'; import { courseMetaDeta } from '$lib/components/Courses/store'; + import { getPathwayCompletedCoursesLength } from '$lib/utils/functions/pathway'; let hasFetched = false; let selectedId = '0'; - let filteredCourses: LmsCourse[] | any[] = []; + let filteredCourses: LMSCourse[] | any[] = []; + let filteredCoursesInProgress: LMSCourse[] = []; + let filteredCoursesCompleted: LMSCourse[] = []; let searching = false; let searchValue = ''; + let currentTab = '1'; function onChange(tab) { + filterCourses(searchValue, selectedId, $lmsCourses); return () => (currentTab = tab); } @@ -49,7 +54,7 @@ isPathway: false })); - const allResults: LmsCourse[] = [...pathwaysWithFlag, ...coursesWithFlag]; + const allResults: LMSCourse[] = [...pathwaysWithFlag, ...coursesWithFlag]; lmsCourses.set(allResults); hasFetched = true; @@ -60,7 +65,7 @@ } } - function filterCourses(searchValue: string, _selectedId: string, courses: any[]) { + function filterCourses(searchValue: string, _selectedId: string, courses: LMSCourse[]) { if (browser) { if (!selectedId) { selectedId = localStorage.getItem('classroomio_filter_lms_mylearning_key') || '0'; @@ -69,22 +74,38 @@ } } - filteredCourses = courses.filter((course) => { - if (!searchValue || course.title.toLowerCase().includes(searchValue.toLowerCase())) { - return true; - } - searching = true; - return false; + const filtered = courses.filter((course) => { + return !searchValue || course.title.toLowerCase().includes(searchValue.toLowerCase()); }); if (_selectedId === '0') { - filteredCourses = filteredCourses.sort( - (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() + filteredCourses = filtered.sort( + (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ); } else if (_selectedId === '1') { - filteredCourses = filteredCourses.sort( - (a, b) => (b.isPathway ? 1 : 0) - (a.isPathway ? 1 : 0) - ); + filteredCourses = filtered.filter((course) => course.isPathway); + } + + // Filter the items render based on the current tab change + if (currentTab === '1') { + filteredCoursesInProgress = filteredCourses.filter((course) => { + if (course.isPathway) { + const incompleteCourses = course.pathway_course.filter((pathwayCourse) => { + const lessons = pathwayCourse.course.lesson; + return lessons.length > 0 && !lessons.every((lesson) => lesson.is_complete); + }); + return incompleteCourses.length > 0; + } + return course.total_lessons !== course.progress_rate; + }); + } else if (currentTab === '2') { + filteredCoursesCompleted = filteredCourses.filter((course) => { + if (course.isPathway) { + const completedCourses = getPathwayCompletedCoursesLength(course); + return completedCourses === course.pathway_course.length; + } + return course.total_lessons === course.progress_rate; + }); } } @@ -93,30 +114,17 @@ filterCourses(searchValue, selectedId, $lmsCourses); } - $: coursesInProgress = - filteredCourses.length > 0 - ? filteredCourses.filter((course) => { - return course.total_lessons !== course.progress_rate; - }) - : []; - $: coursesComplete = - filteredCourses.length > 0 - ? filteredCourses.filter((course) => { - return course.total_lessons == course.progress_rate; - }) - : []; - $: tabs = [ { - label: `${$t('my_learning.progress')} (${coursesInProgress.length})`, + label: `${$t('my_learning.progress')} (${filteredCoursesInProgress.length})`, value: '1' }, { - label: `${$t('my_learning.complete')} (${coursesComplete.length})`, + label: `${$t('my_learning.complete')} (${filteredCoursesCompleted.length})`, value: '2' } ]; - $: currentTab = tabs[0].value; + // $: currentTab = tabs[0].value;
    @@ -149,7 +157,7 @@ @@ -157,7 +165,7 @@ From 3aa03c3004d7c5592b786e2ec18f03ca42686e38 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Sat, 24 Aug 2024 17:10:41 +0100 Subject: [PATCH 11/22] fix; navigation id for courses --- .../src/lib/components/Pathways/components/Sidebar.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte b/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte index 55a5aba36..11258f622 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte @@ -14,10 +14,10 @@ import { courses, pathway } from '../store'; import { t } from '$lib/utils/functions/translations'; + import { getLectureNo } from '$lib/components/Course/function'; import { getIsCourseComplete, getPathwayNavItemRoute } from '../functions'; import TextChip from '$lib/components/Chip/Text.svelte'; - import { getLectureNo } from '$lib/components/Course/function'; import NavExpandable from '$lib/components/Course/components/Navigation/NavExpandable.svelte'; export let path: string; @@ -250,7 +250,7 @@ !item.is_unlocked ? 'cursor-not-allowed' : ''}" - href="/courses/{item.id}" + href="/courses/{item.course_id}" on:click={toggleSidebarOnMobile} aria-disabled={!item.is_unlocked} > From 340e3deea59b7f53e50ed012a99cf9523723c10a Mon Sep 17 00:00:00 2001 From: tunny17 Date: Sat, 24 Aug 2024 22:13:06 +0100 Subject: [PATCH 12/22] fix: unlocking courses bug --- apps/dashboard/src/lib/utils/services/pathways/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 247fbe511..8f03f7a84 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -52,6 +52,7 @@ const ID_QUERY = ` order, course_id, pathway_id, + is_unlocked, course ( id, title, From 1bb13502730562fa892bbaf6ed6d4b59f92cb67c Mon Sep 17 00:00:00 2001 From: emmanuel Date: Sun, 25 Aug 2024 22:00:27 +0100 Subject: [PATCH 13/22] wrapping things up --- .../LMS/components/CourseListModal.svelte | 91 +++++++++---------- .../components/LMS/components/Learning.svelte | 18 ++-- .../src/lib/utils/functions/pathway.ts | 7 ++ 3 files changed, 59 insertions(+), 57 deletions(-) diff --git a/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte b/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte index 07e1303dc..1d61ba3d4 100644 --- a/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte +++ b/apps/dashboard/src/lib/components/LMS/components/CourseListModal.svelte @@ -6,6 +6,7 @@ import { t } from '$lib/utils/functions/translations'; import { goto } from '$app/navigation'; import type { Pathway } from '$lib/utils/types'; + import { courseProgress } from '$lib/utils/functions/pathway'; export let open = false; export let pathway: Pathway | any; @@ -13,13 +14,6 @@ const gotoPathway = () => { return goto(`/pathways/${pathway.id}`); }; - - const courseProgress = (lessons) => { - const totalLesson = lessons.length; - const completedLesson = lessons.filter((lesson) => lesson.is_complete).length; - - return (completedLesson / totalLesson) * 100; - }; -
    - {#each pathway.pathway_course as pathway_course} - -
    -

    {pathway_course.course.title}

    - {#if pathway_course.is_unlocked == false || pathway_course.course.is_published == false} -

    - {$t('lms_pathway.locked')} -

    - {:else} -
    + {:else} +

    {$t('search.no_course')}

    + {/if}
    - + {#if pathway.pathway_course?.length > 0} + + {/if}
    diff --git a/apps/dashboard/src/lib/components/LMS/components/Learning.svelte b/apps/dashboard/src/lib/components/LMS/components/Learning.svelte index 693c381da..6fc7b3150 100644 --- a/apps/dashboard/src/lib/components/LMS/components/Learning.svelte +++ b/apps/dashboard/src/lib/components/LMS/components/Learning.svelte @@ -7,19 +7,15 @@ import { ChevronDown } from 'carbon-icons-svelte'; import CourseListModal from '$lib/components/LMS/components/CourseListModal.svelte'; import { lmsCourses } from '$lib/components/LMS/store'; - import { getPathwayCompletedCoursesLength } from '$lib/utils/functions/pathway'; + import { courseProgress, getPathwayCompletedCoursesLength } from '$lib/utils/functions/pathway'; let open = false; - // const getCompletedCoursesCount = (pathway) => { - // return ( - // pathway.pathway_course?.filter((course) => course.course.progress_rate === 100).length || 0 - // ); - // }; - // this return the first course it can find in the array that is been taken but not yet completed. const getCurrentCourse = (pathway) => { - return pathway.pathway_course?.find((course) => course.is_unlocked === true); + return pathway.pathway_course?.find( + (course) => course.is_unlocked === true && courseProgress(course.course.lesson) != 100 + ); }; const gotoCourse = (id: string | undefined) => { @@ -83,15 +79,15 @@ onClick={() => gotoCourse(getCurrentCourse(course)?.course.id)} /> - {#if getCurrentCourse(course)?.course.progress_rate && getCurrentCourse(course)?.course.progress_rate > 0} + {#if getCurrentCourse(course) && courseProgress(getCurrentCourse(course).course.lesson) > 0}
    {/if} - {#if course.pathway_course?.length > 0} + {#if course.pathway_course} {/if} {:else} diff --git a/apps/dashboard/src/lib/utils/functions/pathway.ts b/apps/dashboard/src/lib/utils/functions/pathway.ts index 27aaf8b34..c8c142357 100644 --- a/apps/dashboard/src/lib/utils/functions/pathway.ts +++ b/apps/dashboard/src/lib/utils/functions/pathway.ts @@ -39,3 +39,10 @@ export const getPathwayCompletedCoursesLength = (pathway: Pathway) => { console.log('lesson done', completedCourses); return completedCourses; }; + +export const courseProgress = (lessons) => { + const totalLesson = lessons.length; + const completedLesson = lessons.filter((lesson) => lesson.is_complete).length; + + return (completedLesson / totalLesson) * 100; +}; From 0e92293d6b1f9301abe68f95b8aa0f1a247e1680 Mon Sep 17 00:00:00 2001 From: emmanuel Date: Sun, 25 Aug 2024 22:32:55 +0100 Subject: [PATCH 14/22] still cleaning things up --- .../src/routes/pathways/[id]/settings/+page.svelte | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte index 1ed83ad5b..f6094c614 100644 --- a/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/settings/+page.svelte @@ -75,13 +75,13 @@ } = $pathwaySettings; await updatePathways($pathway.id, avatar, { - title, - logo, - description, - prerequisite, - is_published, - lms_certificate, - courses_certificate, + title: title, + logo: logo, + description: description, + prerequisite: prerequisite, + is_published: is_published, + lms_certificate: lms_certificate, + courses_certificate: courses_certificate, slug: $pathway.slug }); From 60a58e5733b14ec51dbd8878d306d393ef5d378f Mon Sep 17 00:00:00 2001 From: tunny17 Date: Sun, 25 Aug 2024 23:12:28 +0100 Subject: [PATCH 15/22] merge fix --- apps/dashboard/src/lib/utils/services/pathways/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 9dcb2ffcc..00b9598e4 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -219,8 +219,4 @@ export async function updatePathways( await supabase.from('pathway').update(pathway).match({ id: pathwayId }); return pathway.logo; -} - -export async function deletePathway(pathwayId: Pathway['id']) { - return await supabase.from('pathway').update({ status: 'DELETED' }).match({ id: pathwayId }); -} +} \ No newline at end of file From 28a167f285453d6b76c5467925f30b1a009600b2 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Mon, 26 Aug 2024 18:01:33 +0100 Subject: [PATCH 16/22] feat: certificate display --- .../UnlockedCertificate.svelte | 24 ++++++++-------- .../components/RoleBasedSecurity/index.svelte | 28 +++++++++---------- .../src/lib/utils/services/pathways/index.ts | 1 + .../pathways/[id]/certificates/+page.svelte | 2 +- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte b/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte index dce2f648e..2c8d5fb0a 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/Certificate/StudentCertificate/UnlockedCertificate.svelte @@ -18,8 +18,8 @@ import { fetchProfilePathwayProgress } from '$lib/utils/services/pathways'; let isLoading = false; - let showCourses = false; - let isPathwayComplete = false; + let showCourses = true; + let isPathwayComplete = true; let progress: ProfilePathwayProgress | undefined; function toggleCourse() { @@ -84,23 +84,25 @@ $: subtitle = isPathwayComplete ? 'pathway.pages.lms_certificate.unlocked.subtitle' : 'pathway.pages.lms_certificate.unlocked.subtitle'; + + $: console.log('$pathway.pathway_course', $pathway.pathway_course); -
    +
    Certificate -
    +

    {$t(title)}

    {$t(subtitle)}

    -
    +
    -
    +

    Courses Completed

    includes courses you have completed to achieve this learning path

    @@ -161,16 +163,16 @@ OR you still want to allow the students view the listed courses and we desable the download button for uncompleted courses --> - {#if $pathway.courses.length > 0 && showCourses} + {#if $pathway.pathway_course?.length > 0 && showCourses}
    - {#each $pathway.courses as course} + {#each $pathway.pathway_course as course}
    - {course.title} + {course.course.title}
    - {course.description} + {course.course.description}
    - - - - {#if $pathway.pathway_course?.length > 0 && showCourses} + {#if completedCourses?.length > 0 && showCourses}
    - {#each $pathway.pathway_course as course} + {#each completedCourses as course}
    {course.course.title} diff --git a/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte b/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte index 11258f622..2dd546601 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/Sidebar.svelte @@ -250,8 +250,8 @@ !item.is_unlocked ? 'cursor-not-allowed' : ''}" - href="/courses/{item.course_id}" - on:click={toggleSidebarOnMobile} + href={`/courses/${item.course_id}`} + on:click={!item.is_unlocked ? (e) => e.preventDefault() : toggleSidebarOnMobile} aria-disabled={!item.is_unlocked} >
    { + const allProgressData: ProfileCourseProgress[] = []; + const errors: PostgrestError[] = []; + + for (const courseId of courseIds) { + const { data, error } = await fetchProfileCourseProgress(courseId, profileId); + if (data) { + allProgressData.push(...data); + } + if (error) { + console.log('error', error); + } + } + + return { + data: allProgressData.length > 0 ? allProgressData : null, + errors: errors.length > 0 ? errors : null, + }; +} + + const SLUG_QUERY = ` id, title, diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 2cf78490a..8af6beb89 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -158,23 +158,6 @@ export function updatePathwayCourses( .match({ id: id, pathway_id: pathwayId, course_id: courseId }); } -export async function fetchProfilePathwayProgress( - pathwayId, - profileId -): Promise<{ - data: ProfilePathwayProgress[] | null; - error: PostgrestError | null; -}> { - const { data, error } = await supabase - .rpc('get_pathway_progress', { - pathway_id_arg: pathwayId, - profile_id_arg: profileId - }) - .returns(); - - return { data, error }; -} - export async function uploadAvatar(pathwayId: string, avatar: string) { const filename = `pathway/${pathwayId + Date.now()}.webp`; let logo; diff --git a/apps/dashboard/src/lib/utils/types/index.ts b/apps/dashboard/src/lib/utils/types/index.ts index 3222de1ad..1f7c963bb 100644 --- a/apps/dashboard/src/lib/utils/types/index.ts +++ b/apps/dashboard/src/lib/utils/types/index.ts @@ -21,15 +21,6 @@ export interface ProfileCourseProgress { lessons_count: number; } -export interface ProfilePathwayProgress { - exercises_completed: number; - exercises_count: number; - lessons_completed: number; - lessons_count: number; - courses_completed: number; - courses_count: number; -} - export interface GroupPerson { assigned_student_id: number | null; created_at: string; diff --git a/apps/dashboard/src/routes/pathway/[slug]/+page.js b/apps/dashboard/src/routes/pathway/[slug]/+page.js new file mode 100644 index 000000000..52c801e5e --- /dev/null +++ b/apps/dashboard/src/routes/pathway/[slug]/+page.js @@ -0,0 +1,15 @@ +import { supabase, getSupabase } from '$lib/utils/functions/supabase'; +import { fetchPathway } from '$lib/utils/services/pathways'; + +if (!supabase) { + getSupabase(); +} + +export const load = async ({ params = { slug: '' } }) => { + const { data } = await fetchPathway(undefined, params.slug); + + return { + slug: params.slug, + pathway: data + }; +}; diff --git a/apps/dashboard/src/routes/pathway/[slug]/+page.svelte b/apps/dashboard/src/routes/pathway/[slug]/+page.svelte new file mode 100644 index 000000000..e0a4e8209 --- /dev/null +++ b/apps/dashboard/src/routes/pathway/[slug]/+page.svelte @@ -0,0 +1,22 @@ + + + diff --git a/apps/dashboard/src/routes/pathways/[id]/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/+page.svelte index 07e1459f8..90cfda548 100644 --- a/apps/dashboard/src/routes/pathways/[id]/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/+page.svelte @@ -177,8 +177,10 @@ if (feed.id === feedId) { snackbar.success( `${ - newIsPinned ? 'snackbar.course.success.pinned' : 'snackbar.course.success.unpinned' - } snackbar.course.success.successfully` + newIsPinned + ? $t('snackbar.course.success.pinned') + : $t('snackbar.course.success.unpinned') + } ${$t('snackbar.course.success.successfully')}` ); feed.isPinned = newIsPinned; From 2899617e8a8e41eb287d25bda6959844e05adce6 Mon Sep 17 00:00:00 2001 From: tunny17 Date: Mon, 2 Sep 2024 09:07:39 +0100 Subject: [PATCH 22/22] feat: bug fixes --- .../components/LessonCard.svelte | 14 ++ .../components/CourseLandingPage/index.svelte | 77 ++----- .../Pathways/components/AddCourseModal.svelte | 191 ++++++++++++++---- .../components/DragAndDropModal.svelte | 22 -- .../components/PathwayContainer.svelte | 2 + .../PathwayLandingPage/index.svelte | 50 ++--- .../src/lib/components/Pathways/functions.ts | 6 +- .../src/lib/components/Pathways/store.ts | 7 +- .../src/lib/utils/services/pathways/index.ts | 19 +- .../src/lib/utils/translations/en.json | 5 +- apps/dashboard/src/lib/utils/types/index.ts | 2 + .../src/routes/pathways/[id]/+page.svelte | 2 +- .../routes/pathways/[id]/courses/+page.svelte | 12 +- 13 files changed, 234 insertions(+), 175 deletions(-) create mode 100644 apps/dashboard/src/lib/components/CourseLandingPage/components/LessonCard.svelte diff --git a/apps/dashboard/src/lib/components/CourseLandingPage/components/LessonCard.svelte b/apps/dashboard/src/lib/components/CourseLandingPage/components/LessonCard.svelte new file mode 100644 index 000000000..cd62b01f9 --- /dev/null +++ b/apps/dashboard/src/lib/components/CourseLandingPage/components/LessonCard.svelte @@ -0,0 +1,14 @@ + + +
    + +

    + {title} +

    +
    diff --git a/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte b/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte index 3e5889cc4..34404c2ec 100644 --- a/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte +++ b/apps/dashboard/src/lib/components/CourseLandingPage/index.svelte @@ -1,32 +1,30 @@
    + {#if $addCourseModal.step === 0}
    @@ -173,14 +234,57 @@
    - {:else if $addCourseModal.step === 1} - (pathwayCourses = e.detail)} - /> + + + {:else if $addCourseModal.step === 1 && $courses.length > 0} + + + + {:else if $addCourseModal.step === 2} +
    + + + + {$t('pathway.pages.course.body_title')} + {$t('pathway.pages.course.description')} + {$t('pathway.pages.course.lessons')} + {$t('pathway.pages.course.students')} + + + + + {#each $courses as course} + + +
    +
    + c.id === course.id)} + onInputChange={(e) => toggleCourseSelection(course, e.target?.checked)} + /> +
    +

    + {course.course.title} +

    +
    +
    + {course.course.description} + {course.course.lessons?.length} + {course.course.group_id?.groupmember?.length} +
    + {/each} +
    +
    +
    {/if}
    + {#if $addCourseModal.step === 0} - {:else} + + {:else if $addCourseModal.step === 1}
    + + {:else} +
    + +
    {/if}
    diff --git a/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte b/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte index e2f05e7a9..7d0282ee6 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/DragAndDropModal.svelte @@ -1,19 +1,14 @@
    @@ -93,7 +75,3 @@
    {/each}
    - - diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte index 6acb446d7..23bc5ebf4 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayContainer.svelte @@ -40,6 +40,8 @@ const { data: _data } = await fetchPathway(pathwayId); + console.log('dataaaa refetch', _data); + if (_data) { setPathway(_data); } diff --git a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte index d4c57af86..940370b47 100644 --- a/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte +++ b/apps/dashboard/src/lib/components/Pathways/components/PathwayLandingPage/index.svelte @@ -28,6 +28,7 @@ import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte'; import UploadWidget from '$lib/components/UploadWidget/index.svelte'; import CourseIcon from '$lib/components/Icons/CourseIcon.svelte'; + import LessonCard from '$lib/components/CourseLandingPage/components/LessonCard.svelte'; export let editMode = false; export let pathwayData: Pathway; @@ -256,9 +257,9 @@ -
    +
    -
    +

    @@ -291,9 +292,9 @@
    -
    +

    {$t('pathway.pages.landingPage.certificate.earn')}

    @@ -301,11 +302,17 @@ {$t('pathway.pages.landingPage.certificate.evidence')}

    - certificate template + certificate template
    -
    +

    {get(pathwayData, 'title', '')}

    - {#each $pathway.pathway_course as course} -
    -
    -

    {course.course.title}

    -
    - - {course.course.lesson?.length || 0} - {$t('pathway.pages.landingPage.courseList.lessons')} - - - - - {$t('pathway.pages.landingPage.courseList.estimated')} - {course.estimated_hours} - {$t('pathway.pages.landingPage.courseList.hours')} - -
    -
    - -
    - - -
    -
    +
    + {#each $pathway.pathway_course as course, index} + {/each}

    @@ -364,7 +348,7 @@
    {#if reviews && reviews.length > 0} -
    +

    {$t('course.navItem.landing_page.reviews')}

    diff --git a/apps/dashboard/src/lib/components/Pathways/functions.ts b/apps/dashboard/src/lib/components/Pathways/functions.ts index e7ff28402..32fc676b6 100644 --- a/apps/dashboard/src/lib/components/Pathways/functions.ts +++ b/apps/dashboard/src/lib/components/Pathways/functions.ts @@ -1,7 +1,5 @@ -import type { CourseCompletion } from '$lib/utils/types'; - export function getIsCourseComplete( - completions: CourseCompletion[], + completions, profileId: string | undefined ): boolean { if (!Array.isArray(completions)) return false; @@ -19,7 +17,7 @@ export function getPathwayNavItemRoute(pathwayId = '', routeId?: string) { return `${path}/${routeId}`; } -export function timeAgo(timestamp: number): string { +export function timeAgo(timestamp: number | string): string { const date = new Date(timestamp); const now = new Date(); const timeDifference = Math.abs(now.getTime() - date.getTime()); diff --git a/apps/dashboard/src/lib/components/Pathways/store.ts b/apps/dashboard/src/lib/components/Pathways/store.ts index 5ee9e57c8..66dc494d8 100644 --- a/apps/dashboard/src/lib/components/Pathways/store.ts +++ b/apps/dashboard/src/lib/components/Pathways/store.ts @@ -47,7 +47,6 @@ export const defaultPathway: Pathway = { status: 'Published', created_at: new Date().toDateString(), updated_at: new Date().toDateString(), - enrollment_date: '', landingpage: { header: { title: '', @@ -68,7 +67,11 @@ export const defaultPathway: Pathway = { }, allowNewStudent: true, showDiscount: true, - discount: 0 + discount: 0, + goals: '', + requirements: '', + description: '', + videoUrl: '' }, is_certificate_downloadable: false, certificate_theme: 'professional', diff --git a/apps/dashboard/src/lib/utils/services/pathways/index.ts b/apps/dashboard/src/lib/utils/services/pathways/index.ts index 8af6beb89..c1f1de012 100644 --- a/apps/dashboard/src/lib/utils/services/pathways/index.ts +++ b/apps/dashboard/src/lib/utils/services/pathways/index.ts @@ -1,6 +1,6 @@ import { supabase } from '$lib/utils/functions/supabase'; -import type { Pathway, PathwayCourse, ProfilePathwayProgress } from '$lib/utils/types'; -import type { PostgrestError, PostgrestSingleResponse } from '@supabase/supabase-js'; +import type { Pathway, PathwayCourse } from '$lib/utils/types'; +import type { PostgrestSingleResponse } from '@supabase/supabase-js'; export function addPathwayGroupMember(member: any) { return supabase.from('groupmember').insert(member).select(); @@ -134,28 +134,31 @@ export async function deletePathway(pathwayId: Pathway['id']) { return await supabase.from('pathway').update({ status: 'DELETED' }).match({ id: pathwayId }); } -export function addPathwayCourse(pathwayId: Pathway['id'], courseId: PathwayCourse['course_id']) { +export async function deletePathwayCourse(courseId: PathwayCourse['course_id']) { + return supabase.from('pathway_course').delete().match({ course_id: courseId }); +} + +export function addPathwayCourse(pathwayId: Pathway['id'], courseId: PathwayCourse['course_id'], order:PathwayCourse['order']) { return supabase .from('pathway_course') .insert([ { pathway_id: pathwayId, - course_id: courseId + course_id: courseId, + order: order } ]) .select(); } export function updatePathwayCourses( - id: any, - pathwayId: Pathway['id'], - courseId: PathwayCourse['course_id'], + id: PathwayCourse['id'], order: PathwayCourse['order'] ) { return supabase .from('pathway_course') .update({ order: order }) - .match({ id: id, pathway_id: pathwayId, course_id: courseId }); + .match({ id: id }); } export async function uploadAvatar(pathwayId: string, avatar: string) { diff --git a/apps/dashboard/src/lib/utils/translations/en.json b/apps/dashboard/src/lib/utils/translations/en.json index 23d63f5ba..bb4c21a15 100644 --- a/apps/dashboard/src/lib/utils/translations/en.json +++ b/apps/dashboard/src/lib/utils/translations/en.json @@ -46,7 +46,7 @@ "course": { "title": "Courses", "add_course": "Add Course", - "add_remove": "Add / remove course", + "add_remove": "Remove course", "order": "Order Courses", "publish": "Publish pathway", "search": "Find Pathway Course", @@ -170,7 +170,8 @@ "add": "Add", "course": "Course", "courses": "Courses", - "path": "to Learning Path" + "path": "to Learning Path", + "delete": "Confirm delete" }, "dragAndDrop": { "title": "Arrange courses", diff --git a/apps/dashboard/src/lib/utils/types/index.ts b/apps/dashboard/src/lib/utils/types/index.ts index 1f7c963bb..e4c06bd97 100644 --- a/apps/dashboard/src/lib/utils/types/index.ts +++ b/apps/dashboard/src/lib/utils/types/index.ts @@ -277,6 +277,8 @@ export interface PathwayCourse { pathway_id: any; order: number; is_unlocked: boolean; + created_at: string; + updated_at:string; } export interface Pathway { diff --git a/apps/dashboard/src/routes/pathways/[id]/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/+page.svelte index 90cfda548..08944a742 100644 --- a/apps/dashboard/src/routes/pathways/[id]/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/+page.svelte @@ -252,7 +252,7 @@ { - goto(`/pathways/${data.pathwayId}/courses`); + // goto(`/pathways/${data.pathwayId}/courses`); }} > diff --git a/apps/dashboard/src/routes/pathways/[id]/courses/+page.svelte b/apps/dashboard/src/routes/pathways/[id]/courses/+page.svelte index d6c1b77b2..d5badf6a5 100644 --- a/apps/dashboard/src/routes/pathways/[id]/courses/+page.svelte +++ b/apps/dashboard/src/routes/pathways/[id]/courses/+page.svelte @@ -50,6 +50,11 @@ $addCourseModal.step = 0; } + function removeCourses() { + $addCourseModal.open = true; + $addCourseModal.step = 2; + } + // Computed property to filter courses based on the search value $: filteredCourses = $courses.filter( (item) => @@ -77,7 +82,7 @@ {/if} - + -
    +
    -
    +
    {#if $courses.length > 0} {#if coursePreference === 'list'}