CLASSROOMIO-T-88: Mark lesson as complete
This commit is contained in:
@@ -2,14 +2,15 @@
|
|||||||
export let className = '';
|
export let className = '';
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let size = '';
|
export let size = '';
|
||||||
|
export let shape = 'rounded-md';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
class="{className} {size === 'sm'
|
class="{className} {size === 'sm' ? 'py-1 px-1' : 'py-2 px-2'} org-avatar {className.includes(
|
||||||
? 'py-1 px-1'
|
'bg-'
|
||||||
: 'py-2 px-2'} org-avatar {className.includes('bg-')
|
)
|
||||||
? ''
|
? ''
|
||||||
: 'bg-gray-200'} rounded-md text-center"
|
: 'bg-gray-200'} {shape} text-center"
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { Writable, Updater } from 'svelte/store';
|
import type { Writable, Updater } from 'svelte/store';
|
||||||
import {
|
import { createLesson, updateLesson, deleteLesson } from '$lib/utils/services/courses';
|
||||||
createLesson,
|
|
||||||
updateLesson,
|
|
||||||
deleteLesson,
|
|
||||||
} from '$lib/utils/services/courses';
|
|
||||||
|
|
||||||
import type { Lesson, Course } from '$lib/utils/types';
|
import type { Lesson, Course } from '$lib/utils/types';
|
||||||
|
|
||||||
@@ -12,12 +8,13 @@ export const lessons: Writable<Lesson[]> = writable([]);
|
|||||||
export const lesson = writable({
|
export const lesson = writable({
|
||||||
id: null,
|
id: null,
|
||||||
totalExercises: 0,
|
totalExercises: 0,
|
||||||
|
is_complete: false,
|
||||||
materials: {
|
materials: {
|
||||||
note: null,
|
note: null,
|
||||||
slide_url: null,
|
slide_url: null,
|
||||||
video_url: null,
|
video_url: null
|
||||||
},
|
},
|
||||||
exercises: [],
|
exercises: []
|
||||||
});
|
});
|
||||||
export const isLessonDirty = writable(false);
|
export const isLessonDirty = writable(false);
|
||||||
|
|
||||||
@@ -31,8 +28,9 @@ export function handleAddLesson() {
|
|||||||
// profile: undefined,
|
// profile: undefined,
|
||||||
call_url: undefined,
|
call_url: undefined,
|
||||||
lesson_at: new Date(),
|
lesson_at: new Date(),
|
||||||
is_complete: false,
|
is_unlocked: false,
|
||||||
},
|
is_complete: false
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}) as Updater<any>);
|
}) as Updater<any>);
|
||||||
}
|
}
|
||||||
@@ -48,17 +46,12 @@ export function handleDelete(lessonId: Lesson['id']) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lessons.update((_lessons) =>
|
lessons.update((_lessons) => _lessons.filter((lesson) => lesson.id !== lessonId));
|
||||||
_lessons.filter((lesson) => lesson.id !== lessonId)
|
|
||||||
);
|
|
||||||
console.log(`lessonId`, lessonId);
|
console.log(`lessonId`, lessonId);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleSaveLesson(
|
export async function handleSaveLesson(lesson: Lesson, course_id: Course['id']) {
|
||||||
lesson: Lesson,
|
|
||||||
course_id: Course['id']
|
|
||||||
) {
|
|
||||||
console.log(`handleSaveLesson lesson`, lesson);
|
console.log(`handleSaveLesson lesson`, lesson);
|
||||||
const newLesson = {
|
const newLesson = {
|
||||||
title: lesson.title,
|
title: lesson.title,
|
||||||
@@ -66,10 +59,11 @@ export async function handleSaveLesson(
|
|||||||
call_url: lesson.call_url,
|
call_url: lesson.call_url,
|
||||||
teacher_id: lesson.profile ? lesson.profile.id : undefined,
|
teacher_id: lesson.profile ? lesson.profile.id : undefined,
|
||||||
course_id,
|
course_id,
|
||||||
is_complete: lesson.is_complete,
|
is_unlocked: lesson.is_unlocked,
|
||||||
|
is_complete: lesson.is_complete
|
||||||
};
|
};
|
||||||
|
|
||||||
let updatedLesson: any[] | null = null;
|
let newLessonData: any[] | null = null;
|
||||||
|
|
||||||
if (typeof lesson.id === 'string') {
|
if (typeof lesson.id === 'string') {
|
||||||
// No need to get the result of update cause we have all in local state
|
// No need to get the result of update cause we have all in local state
|
||||||
@@ -77,26 +71,25 @@ export async function handleSaveLesson(
|
|||||||
} else {
|
} else {
|
||||||
const { data } = await createLesson(newLesson);
|
const { data } = await createLesson(newLesson);
|
||||||
|
|
||||||
updatedLesson = data;
|
newLessonData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
lessons.update((l) =>
|
lessons.update((l) =>
|
||||||
l.map((lesson) => {
|
l.map((_lesson) => {
|
||||||
if (!lesson.id && updatedLesson) {
|
if (!_lesson.id && newLessonData) {
|
||||||
lesson.id = updatedLesson[0].id;
|
_lesson.id = newLessonData[0].id;
|
||||||
|
} else if (typeof lesson.id === 'string' && lesson.id === _lesson.id) {
|
||||||
|
return { ..._lesson, is_complete: lesson.is_complete };
|
||||||
}
|
}
|
||||||
|
|
||||||
return lesson;
|
return _lesson;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleUpdateLessonMaterials(
|
export async function handleUpdateLessonMaterials(lesson: any, lessonId: Lesson['id']) {
|
||||||
lesson: any,
|
|
||||||
lessonId: Lesson['id']
|
|
||||||
) {
|
|
||||||
const materials = {
|
const materials = {
|
||||||
...lesson.materials,
|
...lesson.materials
|
||||||
};
|
};
|
||||||
|
|
||||||
delete materials.totalExercises;
|
delete materials.totalExercises;
|
||||||
|
|||||||
@@ -15,20 +15,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div
|
<button
|
||||||
class="item relative flex items-center {isGroupActive && 'active'}"
|
class="item relative flex items-center {isGroupActive && 'active'} w-full"
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
>
|
>
|
||||||
{#if !hideSortIcon}
|
|
||||||
<CaretSortDownIcon
|
|
||||||
size={20}
|
|
||||||
class="carbon-icon-sort {isExpanded ? '' : 'carbon-icon-minimize'}"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<span class="dark:text-white font-bold">{label}</span>
|
<span class="dark:text-white font-bold">{label}</span>
|
||||||
</div>
|
</button>
|
||||||
{#if isExpanded}
|
{#if isExpanded}
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import hotkeys from 'hotkeys-js';
|
import hotkeys from 'hotkeys-js';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte';
|
||||||
import HomeIcon from 'carbon-icons-svelte/lib/Home.svelte';
|
import HomeIcon from 'carbon-icons-svelte/lib/Home.svelte';
|
||||||
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
import CheckmarkOutline from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
|
||||||
import SettingsIcon from 'carbon-icons-svelte/lib/Settings.svelte';
|
import SettingsIcon from 'carbon-icons-svelte/lib/Settings.svelte';
|
||||||
import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte';
|
import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte';
|
||||||
import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte';
|
import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte';
|
||||||
@@ -13,12 +14,9 @@
|
|||||||
import PageNav from '$lib/components/PageNav/index.svelte';
|
import PageNav from '$lib/components/PageNav/index.svelte';
|
||||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||||
import {
|
import { getNavItemRoute, getLessonsRoute, getLectureNo } from '$lib/components/Course/function';
|
||||||
getNavItemRoute,
|
|
||||||
getLessonsRoute,
|
|
||||||
getLectureNo,
|
|
||||||
} from '$lib/components/Course/function';
|
|
||||||
|
|
||||||
|
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||||
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
||||||
import Settings from '../Settings/index.svelte';
|
import Settings from '../Settings/index.svelte';
|
||||||
import { settingsDialog } from '../Settings/store';
|
import { settingsDialog } from '../Settings/store';
|
||||||
@@ -47,7 +45,7 @@
|
|||||||
|
|
||||||
function getHref(item) {
|
function getHref(item) {
|
||||||
return item.to;
|
return item.to;
|
||||||
// return item.is_complete ? item.to : path || $page.url.pathname
|
// return item.is_unlocked ? item.to : path || $page.url.pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSaveTitle() {
|
function handleSaveTitle() {
|
||||||
@@ -89,18 +87,18 @@
|
|||||||
{
|
{
|
||||||
label: 'Overview',
|
label: 'Overview',
|
||||||
to: getNavItemRoute($course.id),
|
to: getNavItemRoute($course.id),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Lessons',
|
label: 'Lessons',
|
||||||
to: getLessonsRoute($course.id),
|
to: getLessonsRoute($course.id),
|
||||||
hideSortIcon: false,
|
hideSortIcon: false,
|
||||||
isLecture: true,
|
isLecture: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Attendance',
|
label: 'Attendance',
|
||||||
to: getNavItemRoute($course.id, 'attendance'),
|
to: getNavItemRoute($course.id, 'attendance'),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Submissions',
|
label: 'Submissions',
|
||||||
@@ -108,12 +106,12 @@
|
|||||||
hideSortIcon: true,
|
hideSortIcon: true,
|
||||||
show() {
|
show() {
|
||||||
return !isStudent;
|
return !isStudent;
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Marks',
|
label: 'Marks',
|
||||||
to: getNavItemRoute($course.id, 'marks'),
|
to: getNavItemRoute($course.id, 'marks'),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// label: 'Scoreboard',
|
// label: 'Scoreboard',
|
||||||
@@ -123,12 +121,12 @@
|
|||||||
{
|
{
|
||||||
label: 'People',
|
label: 'People',
|
||||||
to: getNavItemRoute($course.id, 'people'),
|
to: getNavItemRoute($course.id, 'people'),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Certificates',
|
label: 'Certificates',
|
||||||
to: getNavItemRoute($course.id, 'certificates'),
|
to: getNavItemRoute($course.id, 'certificates'),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Landing Page',
|
label: 'Landing Page',
|
||||||
@@ -136,17 +134,15 @@
|
|||||||
hideSortIcon: true,
|
hideSortIcon: true,
|
||||||
show() {
|
show() {
|
||||||
return !isStudent;
|
return !isStudent;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Settings />
|
<Settings />
|
||||||
|
|
||||||
<div
|
<div class="root z-10 {!show && 'hide'} {$isMobile ? 'fixed shadow-xl' : 'sticky'}">
|
||||||
class="root z-10 {!show && 'hide'} {$isMobile ? 'fixed shadow-xl' : 'sticky'}"
|
|
||||||
>
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<div class="relative h-full dark:bg-gray-700">
|
<div class="relative h-full dark:bg-gray-700">
|
||||||
<PageNav bind:title={$course.title} paddingClass="pl-2">
|
<PageNav bind:title={$course.title} paddingClass="pl-2">
|
||||||
@@ -155,9 +151,7 @@
|
|||||||
</slot:fragment>
|
</slot:fragment>
|
||||||
<slot:fragment slot="widget">
|
<slot:fragment slot="widget">
|
||||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
<IconButton
|
<IconButton onClick={() => ($settingsDialog.open = !$settingsDialog.open)}>
|
||||||
onClick={() => ($settingsDialog.open = !$settingsDialog.open)}
|
|
||||||
>
|
|
||||||
<SettingsIcon size={20} class="carbon-icon" />
|
<SettingsIcon size={20} class="carbon-icon" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</RoleBasedSecurity>
|
</RoleBasedSecurity>
|
||||||
@@ -175,13 +169,11 @@
|
|||||||
{#if navItem.isLecture}
|
{#if navItem.isLecture}
|
||||||
{#each $lessons as item, index}
|
{#each $lessons as item, index}
|
||||||
<a
|
<a
|
||||||
class="item flex items-center {(
|
class="item flex items-center {(path || $page.url.pathname).includes(item.id) &&
|
||||||
path || $page.url.pathname
|
'active'} pl-7 py-3 {isStudent && !item.is_unlocked
|
||||||
).includes(item.id) && 'active'} pl-7 py-3 {isStudent &&
|
|
||||||
!item.is_complete
|
|
||||||
? 'cursor-not-allowed'
|
? 'cursor-not-allowed'
|
||||||
: ''}"
|
: ''}"
|
||||||
href={isStudent && !item.is_complete
|
href={isStudent && !item.is_unlocked
|
||||||
? $page.url.pathname
|
? $page.url.pathname
|
||||||
: getLessonsRoute($course.id, item.id)}
|
: getLessonsRoute($course.id, item.id)}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@@ -189,17 +181,23 @@
|
|||||||
setTimeout(() => (show = false), 500);
|
setTimeout(() => (show = false), 500);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
aria-disabled={!item.is_unlocked}
|
||||||
>
|
>
|
||||||
<span class="course-counter">
|
<TextChip
|
||||||
{getLectureNo(index + 1)}
|
value={getLectureNo(index + 1)}
|
||||||
</span>
|
className="bg-blue-200 text-xs mr-2"
|
||||||
|
size="sm"
|
||||||
|
shape="rounded-full"
|
||||||
|
/>
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
{#if item.is_complete}
|
{#if !item.is_unlocked}
|
||||||
<span class="ml-2 success">
|
<span class="text-md ml-2">
|
||||||
<CheckmarkFilledIcon size={20} class="carbon-icon" />
|
<LockedIcon class="carbon-icon" />
|
||||||
|
</span>
|
||||||
|
{:else if item.is_complete}
|
||||||
|
<span class="ml-2">
|
||||||
|
<CheckmarkOutline class="carbon-icon" />
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
|
||||||
<span class="text-md ml-2">🔒</span>
|
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -209,9 +207,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="w-full footer p-3 absolute bottom-2 flex items-center justify-between">
|
||||||
class="w-full footer p-3 absolute bottom-2 flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
value="toggle"
|
value="toggle"
|
||||||
onClick={() => goto(`${$currentOrgPath}/courses`)}
|
onClick={() => goto(`${$currentOrgPath}/courses`)}
|
||||||
@@ -236,7 +232,7 @@
|
|||||||
: {
|
: {
|
||||||
title: 'Toggle sidebar',
|
title: 'Toggle sidebar',
|
||||||
hotkeys: ['B'],
|
hotkeys: ['B'],
|
||||||
direction: 'right',
|
direction: 'right'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if show}
|
{#if show}
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ export function getLessonsRoute(courseId, lessonId) {
|
|||||||
return `${path}/${lessonId}`;
|
return `${path}/${lessonId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLectureNo(index, initNo = '00') {
|
export function getLectureNo(index, initNo = '0') {
|
||||||
if (index <= 9) {
|
if (index <= 9) {
|
||||||
return `${initNo}${index}`;
|
return `${initNo}${index}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${initNo.replace('0', '')}${index}`;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatAnswers(data) {
|
export function formatAnswers(data) {
|
||||||
@@ -46,9 +46,7 @@ export function formatAnswers(data) {
|
|||||||
const questionName = questionByIdAndName[answer.question_id];
|
const questionName = questionByIdAndName[answer.question_id];
|
||||||
|
|
||||||
answers[questionName] =
|
answers[questionName] =
|
||||||
Array.isArray(answer.answers) && answer.answers.length
|
Array.isArray(answer.answers) && answer.answers.length ? answer.answers : answer.open_answer;
|
||||||
? answer.answers
|
|
||||||
: answer.open_answer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return answers;
|
return answers;
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
|
|
||||||
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div
|
<div class="head dark:bg-gray-800 flex items-center justify-between sticky right-0 w-full px-3">
|
||||||
class="head dark:bg-gray-800 flex items-center justify-between sticky right-0 w-full px-3"
|
|
||||||
>
|
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ async function cloneGroupAndBasicCourse(
|
|||||||
lessons: undefined,
|
lessons: undefined,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
updated_at: undefined,
|
updated_at: undefined,
|
||||||
created_at: undefined,
|
created_at: undefined
|
||||||
})
|
})
|
||||||
.select();
|
.select();
|
||||||
|
|
||||||
@@ -85,34 +85,30 @@ async function cloneGroupAndBasicCourse(
|
|||||||
profile_id: profileStoreValue.id,
|
profile_id: profileStoreValue.id,
|
||||||
email: profileStoreValue.email,
|
email: profileStoreValue.email,
|
||||||
group_id,
|
group_id,
|
||||||
role_id: ROLE.TUTOR,
|
role_id: ROLE.TUTOR
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('profileStoreValue', profileStoreValue);
|
console.log('profileStoreValue', profileStoreValue);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
newCourse: newCourse?.[0],
|
newCourse: newCourse?.[0]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cloneLessons(lessons: Lesson[], courseId: Course['id']) {
|
async function cloneLessons(lessons: Lesson[], courseId: Course['id']) {
|
||||||
const clonedLessons = lessons
|
const clonedLessons = lessons
|
||||||
.sort(
|
.sort((a, b) => new Date(a.created_at || '').getTime() - new Date(b.created_at || '').getTime())
|
||||||
(a, b) =>
|
|
||||||
new Date(a.created_at || '').getTime() -
|
|
||||||
new Date(b.created_at || '').getTime()
|
|
||||||
)
|
|
||||||
.map((lesson, index) => ({
|
.map((lesson, index) => ({
|
||||||
call_url: lesson.call_url,
|
call_url: lesson.call_url,
|
||||||
course_id: courseId,
|
course_id: courseId,
|
||||||
is_complete: lesson.is_complete,
|
is_unlocked: lesson.is_unlocked,
|
||||||
lesson_at: new Date(),
|
lesson_at: new Date(),
|
||||||
note: lesson.note,
|
note: lesson.note,
|
||||||
order: lesson.order || index + 1,
|
order: lesson.order || index + 1,
|
||||||
public: lesson.public,
|
public: lesson.public,
|
||||||
slide_url: lesson.slide_url,
|
slide_url: lesson.slide_url,
|
||||||
title: lesson.title,
|
title: lesson.title,
|
||||||
video_url: lesson.video_url,
|
video_url: lesson.video_url
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { data } = await supabase.from('lesson').insert(clonedLessons).select();
|
const { data } = await supabase.from('lesson').insert(clonedLessons).select();
|
||||||
@@ -121,7 +117,7 @@ async function cloneLessons(lessons: Lesson[], courseId: Course['id']) {
|
|||||||
return {
|
return {
|
||||||
...dataItem,
|
...dataItem,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
exercise: lessons[dataIndex].exercise,
|
exercise: lessons[dataIndex].exercise
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,7 +143,7 @@ async function cloneExercises(lessons: Lesson[]) {
|
|||||||
description,
|
description,
|
||||||
due_by: new Date(),
|
due_by: new Date(),
|
||||||
lesson_id: lessonId,
|
lesson_id: lessonId,
|
||||||
title,
|
title
|
||||||
})
|
})
|
||||||
.select();
|
.select();
|
||||||
const newExercise = Array.isArray(data) ? data[0] : data;
|
const newExercise = Array.isArray(data) ? data[0] : data;
|
||||||
@@ -166,7 +162,7 @@ async function cloneExercises(lessons: Lesson[]) {
|
|||||||
points: question.points,
|
points: question.points,
|
||||||
order: question.order,
|
order: question.order,
|
||||||
question_type_id: question.question_type_id,
|
question_type_id: question.question_type_id,
|
||||||
exercise_id: newExercise.id,
|
exercise_id: newExercise.id
|
||||||
})
|
})
|
||||||
.select();
|
.select();
|
||||||
const newQuestion = Array.isArray(data) ? data[0] : data;
|
const newQuestion = Array.isArray(data) ? data[0] : data;
|
||||||
@@ -179,7 +175,7 @@ async function cloneExercises(lessons: Lesson[]) {
|
|||||||
value: option.value,
|
value: option.value,
|
||||||
label: option.label,
|
label: option.label,
|
||||||
is_correct: option.is_correct,
|
is_correct: option.is_correct,
|
||||||
question_id: newQuestion.id,
|
question_id: newQuestion.id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Insert[Batch]
|
// Insert[Batch]
|
||||||
@@ -192,10 +188,7 @@ async function cloneExercises(lessons: Lesson[]) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cloneCourse(
|
export async function cloneCourse(courseId: string, newTitle: string): Promise<Course> {
|
||||||
courseId: string,
|
|
||||||
newTitle: string
|
|
||||||
): Promise<Course> {
|
|
||||||
const { data: course, error } = await fetchCourseData(courseId);
|
const { data: course, error } = await fetchCourseData(courseId);
|
||||||
console.log(`course`, course);
|
console.log(`course`, course);
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
import { isUUID } from '$lib/utils/functions/isUUID';
|
import { isUUID } from '$lib/utils/functions/isUUID';
|
||||||
import { QUESTION_TYPE } from '$lib/components/Question/constants';
|
import { QUESTION_TYPE } from '$lib/components/Question/constants';
|
||||||
import type {
|
import type { Lesson, Course, Group, Groupmember, Exercise } from '$lib/utils/types';
|
||||||
Lesson,
|
|
||||||
Course,
|
|
||||||
Group,
|
|
||||||
Groupmember,
|
|
||||||
Exercise,
|
|
||||||
} from '$lib/utils/types';
|
|
||||||
|
|
||||||
export async function fetchCourse(
|
export async function fetchCourse(courseId?: Course['id'], slug?: Course['slug']) {
|
||||||
courseId?: Course['id'],
|
|
||||||
slug?: Course['slug']
|
|
||||||
) {
|
|
||||||
let match: { slug?: string; id?: string } = {};
|
let match: { slug?: string; id?: string } = {};
|
||||||
|
|
||||||
if (slug) {
|
if (slug) {
|
||||||
@@ -42,7 +33,7 @@ export async function fetchCourse(
|
|||||||
is_certificate_downloadable,
|
is_certificate_downloadable,
|
||||||
certificate_theme,
|
certificate_theme,
|
||||||
lessons:lesson(
|
lessons:lesson(
|
||||||
id, title,public, lesson_at, call_url, is_complete, order, created_at,
|
id, title,public, lesson_at, call_url, is_unlocked, is_complete, order, created_at,
|
||||||
note, video_url, slide_url, call_url, totalExercises:exercise(count),
|
note, video_url, slide_url, call_url, totalExercises:exercise(count),
|
||||||
profile:teacher_id(id, avatar_url, fullname)
|
profile:teacher_id(id, avatar_url, fullname)
|
||||||
),
|
),
|
||||||
@@ -61,7 +52,7 @@ export async function fetchCourse(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
error,
|
error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,25 +65,22 @@ export async function fetchGroup(groupId: Group['id']) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
error,
|
error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setProfileIdOfGroupMember(
|
export async function setProfileIdOfGroupMember(email: string, profileId: string) {
|
||||||
email: string,
|
|
||||||
profileId: string
|
|
||||||
) {
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('groupmember')
|
.from('groupmember')
|
||||||
.update({
|
.update({
|
||||||
email: null,
|
email: null,
|
||||||
profile_id: profileId,
|
profile_id: profileId
|
||||||
})
|
})
|
||||||
.match({ email: email });
|
.match({ email: email });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
error,
|
error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,17 +88,13 @@ export async function uploadAvatar(courseId: string, avatar: string) {
|
|||||||
const filename = `course/${courseId + Date.now()}.webp`;
|
const filename = `course/${courseId + Date.now()}.webp`;
|
||||||
let logo;
|
let logo;
|
||||||
|
|
||||||
const { data } = await supabase.storage
|
const { data } = await supabase.storage.from('avatars').upload(filename, avatar, {
|
||||||
.from('avatars')
|
cacheControl: '3600',
|
||||||
.upload(filename, avatar, {
|
upsert: false
|
||||||
cacheControl: '3600',
|
});
|
||||||
upsert: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data && data.Key) {
|
if (data && data.Key) {
|
||||||
const { publicURL } = supabase.storage
|
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
|
||||||
.from('avatars')
|
|
||||||
.getPublicUrl(filename);
|
|
||||||
|
|
||||||
if (!publicURL) return;
|
if (!publicURL) return;
|
||||||
|
|
||||||
@@ -128,17 +112,13 @@ export async function updateCourse(
|
|||||||
if (avatar) {
|
if (avatar) {
|
||||||
const filename = `course/${courseId + Date.now()}.webp`;
|
const filename = `course/${courseId + Date.now()}.webp`;
|
||||||
|
|
||||||
const { data } = await supabase.storage
|
const { data } = await supabase.storage.from('avatars').upload(filename, avatar, {
|
||||||
.from('avatars')
|
cacheControl: '3600',
|
||||||
.upload(filename, avatar, {
|
upsert: false
|
||||||
cacheControl: '3600',
|
});
|
||||||
upsert: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data && data.Key) {
|
if (data && data.Key) {
|
||||||
const { publicURL } = supabase.storage
|
const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
|
||||||
.from('avatars')
|
|
||||||
.getPublicUrl(filename);
|
|
||||||
|
|
||||||
if (!publicURL) return;
|
if (!publicURL) return;
|
||||||
|
|
||||||
@@ -170,9 +150,7 @@ export function deleteGroupMember(groupMemberId: Groupmember['id']) {
|
|||||||
export function fetchLesson(lessonId: Lesson['id']) {
|
export function fetchLesson(lessonId: Lesson['id']) {
|
||||||
return supabase
|
return supabase
|
||||||
.from('lesson')
|
.from('lesson')
|
||||||
.select(
|
.select(`id, note, video_url, slide_url, call_url, totalExercises:exercise(count)`)
|
||||||
`id, note, video_url, slide_url, call_url, totalExercises:exercise(count)`
|
|
||||||
)
|
|
||||||
.eq('id', lessonId)
|
.eq('id', lessonId)
|
||||||
.single();
|
.single();
|
||||||
}
|
}
|
||||||
@@ -206,10 +184,7 @@ function isNew(item: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add questionnaire type
|
// TODO: Add questionnaire type
|
||||||
export async function upsertExercise(
|
export async function upsertExercise(questionnaire: any, exerciseId: Exercise['id']) {
|
||||||
questionnaire: any,
|
|
||||||
exerciseId: Exercise['id']
|
|
||||||
) {
|
|
||||||
const {
|
const {
|
||||||
questions,
|
questions,
|
||||||
title,
|
title,
|
||||||
@@ -217,7 +192,7 @@ export async function upsertExercise(
|
|||||||
due_by,
|
due_by,
|
||||||
is_title_dirty,
|
is_title_dirty,
|
||||||
is_description_dirty,
|
is_description_dirty,
|
||||||
is_due_by_dirty,
|
is_due_by_dirty
|
||||||
} = questionnaire;
|
} = questionnaire;
|
||||||
|
|
||||||
if (is_description_dirty || is_title_dirty || is_due_by_dirty) {
|
if (is_description_dirty || is_title_dirty || is_due_by_dirty) {
|
||||||
@@ -226,7 +201,7 @@ export async function upsertExercise(
|
|||||||
.update({
|
.update({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
due_by,
|
due_by
|
||||||
})
|
})
|
||||||
.match({ id: exerciseId });
|
.match({ id: exerciseId });
|
||||||
}
|
}
|
||||||
@@ -234,31 +209,16 @@ export async function upsertExercise(
|
|||||||
const updatedQuestions = [];
|
const updatedQuestions = [];
|
||||||
|
|
||||||
for (const question of questions) {
|
for (const question of questions) {
|
||||||
const {
|
const { title, id, name, question_type, options, deleted_at, order, points, is_dirty } =
|
||||||
title,
|
question;
|
||||||
id,
|
|
||||||
name,
|
|
||||||
question_type,
|
|
||||||
options,
|
|
||||||
deleted_at,
|
|
||||||
order,
|
|
||||||
points,
|
|
||||||
is_dirty,
|
|
||||||
} = question;
|
|
||||||
|
|
||||||
// DELETE /delete/:questionId - Don't delete if answer already given
|
// DELETE /delete/:questionId - Don't delete if answer already given
|
||||||
if (deleted_at) {
|
if (deleted_at) {
|
||||||
// Delete from server only if this question exists in the database
|
// Delete from server only if this question exists in the database
|
||||||
if (!isNew(id)) {
|
if (!isNew(id)) {
|
||||||
await supabase.from('option').delete().match({ question_id: id });
|
await supabase.from('option').delete().match({ question_id: id });
|
||||||
await supabase
|
await supabase.from('question_answer').delete().match({ question_id: id });
|
||||||
.from('question_answer')
|
const { error } = await supabase.from('question').delete().match({ id });
|
||||||
.delete()
|
|
||||||
.match({ question_id: id });
|
|
||||||
const { error } = await supabase
|
|
||||||
.from('question')
|
|
||||||
.delete()
|
|
||||||
.match({ id });
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Cannot delete this question', error);
|
console.error('Cannot delete this question', error);
|
||||||
@@ -278,7 +238,7 @@ export async function upsertExercise(
|
|||||||
points,
|
points,
|
||||||
order,
|
order,
|
||||||
question_type_id: question_type.id,
|
question_type_id: question_type.id,
|
||||||
exercise_id: exerciseId,
|
exercise_id: exerciseId
|
||||||
};
|
};
|
||||||
let questionSupabaseRes;
|
let questionSupabaseRes;
|
||||||
|
|
||||||
@@ -329,7 +289,7 @@ export async function upsertExercise(
|
|||||||
is_dirty: undefined,
|
is_dirty: undefined,
|
||||||
id: isNew(option.id) ? undefined : option.id,
|
id: isNew(option.id) ? undefined : option.id,
|
||||||
value: isUUID(option.value) ? option.value : undefined, // this value is of UUID type
|
value: isUUID(option.value) ? option.value : undefined, // this value is of UUID type
|
||||||
question_id: newQuestion.id,
|
question_id: newQuestion.id
|
||||||
};
|
};
|
||||||
|
|
||||||
if (is_dirty || isNew(option.id)) {
|
if (is_dirty || isNew(option.id)) {
|
||||||
@@ -378,7 +338,7 @@ export async function submitExercise(
|
|||||||
.insert({
|
.insert({
|
||||||
submitted_by: groupMemberId,
|
submitted_by: groupMemberId,
|
||||||
exercise_id: exerciseId,
|
exercise_id: exerciseId,
|
||||||
course_id: courseId,
|
course_id: courseId
|
||||||
})
|
})
|
||||||
.select();
|
.select();
|
||||||
|
|
||||||
@@ -390,7 +350,7 @@ export async function submitExercise(
|
|||||||
question_id: questionsByName[questionName],
|
question_id: questionsByName[questionName],
|
||||||
open_answer: '',
|
open_answer: '',
|
||||||
answers: [],
|
answers: [],
|
||||||
submission_id: Array.isArray(submission) ? submission[0].id : null,
|
submission_id: Array.isArray(submission) ? submission[0].id : null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
@@ -402,17 +362,11 @@ export async function submitExercise(
|
|||||||
questionAnswers.push(questionAnswer);
|
questionAnswers.push(questionAnswer);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await supabase
|
const res = await supabase.from('question_answer').insert(questionAnswers).select();
|
||||||
.from('question_answer')
|
|
||||||
.insert(questionAnswers)
|
|
||||||
.select();
|
|
||||||
console.log(`res`, res);
|
console.log(`res`, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteExercise(
|
export async function deleteExercise(questions: Array<{ id: string }>, exerciseId: Exercise['id']) {
|
||||||
questions: Array<{ id: string }>,
|
|
||||||
exerciseId: Exercise['id']
|
|
||||||
) {
|
|
||||||
for (const question of questions) {
|
for (const question of questions) {
|
||||||
const { id } = question;
|
const { id } = question;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type UserLessonDataType = {
|
|||||||
lesson_at: string;
|
lesson_at: string;
|
||||||
lesson_id: string;
|
lesson_id: string;
|
||||||
lesson_title: string;
|
lesson_title: string;
|
||||||
is_complete: boolean;
|
is_unlocked: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type LessonByDayType = {
|
type LessonByDayType = {
|
||||||
|
|||||||
@@ -141,13 +141,14 @@ export interface Lesson {
|
|||||||
video_url?: any; // type unknown;
|
video_url?: any; // type unknown;
|
||||||
slide_url?: any; // type unknown;
|
slide_url?: any; // type unknown;
|
||||||
course_id: string /* foreign key to course.id */;
|
course_id: string /* foreign key to course.id */;
|
||||||
id: string /* primary key */;
|
id?: string /* primary key */;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
title: any; // type unknown;
|
title: any; // type unknown;
|
||||||
public?: boolean;
|
public?: boolean;
|
||||||
lesson_at?: string;
|
lesson_at?: string;
|
||||||
teacher_id?: string /* foreign key to profile.id */;
|
teacher_id?: string /* foreign key to profile.id */;
|
||||||
|
is_unlocked?: boolean;
|
||||||
is_complete?: boolean;
|
is_complete?: boolean;
|
||||||
call_url?: string;
|
call_url?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
|
|||||||
@@ -181,8 +181,9 @@
|
|||||||
|
|
||||||
$appStore.isStudentDomain = debug || answer;
|
$appStore.isStudentDomain = debug || answer;
|
||||||
$appStore.siteNameFromDomain = debug ? 'codingdojo' : subdomain;
|
$appStore.siteNameFromDomain = debug ? 'codingdojo' : subdomain;
|
||||||
|
} else if (subdomain === 'play' || debug) {
|
||||||
|
skipAuth = true;
|
||||||
}
|
}
|
||||||
skipAuth = debug || subdomain === 'play';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
if (event === 'SIGNED_IN') {
|
if (event === 'SIGNED_IN') {
|
||||||
$user.fetchingUser = true;
|
$user.fetchingUser = true;
|
||||||
getProfile();
|
getProfile();
|
||||||
} else {
|
} else if (!['INITIAL_SESSION', 'TOKEN_REFRESHED'].includes(event)) {
|
||||||
console.log('not logged in, go to login');
|
console.log('not logged in, go to login');
|
||||||
return goto('/login');
|
return goto('/login');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { dndzone } from 'svelte-dnd-action';
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
|
import UnlockedIcon from 'carbon-icons-svelte/lib/Unlocked.svelte';
|
||||||
|
import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte';
|
||||||
import TimeIcon from 'carbon-icons-svelte/lib/Time.svelte';
|
import TimeIcon from 'carbon-icons-svelte/lib/Time.svelte';
|
||||||
import EditIcon from 'carbon-icons-svelte/lib/Edit.svelte';
|
import EditIcon from 'carbon-icons-svelte/lib/Edit.svelte';
|
||||||
import SaveIcon from 'carbon-icons-svelte/lib/Save.svelte';
|
import SaveIcon from 'carbon-icons-svelte/lib/Save.svelte';
|
||||||
import PhoneIcon from 'carbon-icons-svelte/lib/Phone.svelte';
|
import PhoneIcon from 'carbon-icons-svelte/lib/Phone.svelte';
|
||||||
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||||
import CheckmarkFilled24 from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
|
||||||
import PageNav from '$lib/components/PageNav/index.svelte';
|
import PageNav from '$lib/components/PageNav/index.svelte';
|
||||||
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
||||||
import Box from '$lib/components/Box/index.svelte';
|
import Box from '$lib/components/Box/index.svelte';
|
||||||
@@ -137,20 +138,20 @@
|
|||||||
<IconButton
|
<IconButton
|
||||||
disabled={isStudent}
|
disabled={isStudent}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
lesson.is_complete = !lesson.is_complete;
|
lesson.is_unlocked = !lesson.is_unlocked;
|
||||||
handleSaveLesson(lesson, $course.id);
|
handleSaveLesson(lesson, $course.id);
|
||||||
}}
|
}}
|
||||||
toolTipProps={isStudent
|
toolTipProps={isStudent
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
title: `Click to ${lesson.is_complete ? 'lock' : 'unlock'}`,
|
title: `Click to ${lesson.is_unlocked ? 'lock' : 'unlock'}`,
|
||||||
direction: 'right'
|
direction: 'right'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if lesson.is_complete}
|
{#if lesson.is_unlocked}
|
||||||
<CheckmarkFilled24 class="carbon-icon" />
|
<UnlockedIcon size={24} class="carbon-icon" />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-2xl">🔒</span>
|
<LockedIcon size={24} class="carbon-icon" />
|
||||||
{/if}
|
{/if}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,11 +187,11 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<h3 class="dark:text-white text-2xl m-0 flex items-center">
|
<h3 class="dark:text-white text-2xl m-0 flex items-center">
|
||||||
<a
|
<a
|
||||||
href={isStudent && !lesson.is_complete
|
href={isStudent && !lesson.is_unlocked
|
||||||
? $page.url.pathname
|
? $page.url.pathname
|
||||||
: '/courses/' + $course.id + '/lessons/' + lesson.id}
|
: '/courses/' + $course.id + '/lessons/' + lesson.id}
|
||||||
class="dark:text-white hover:underline text-black {isStudent &&
|
class="dark:text-white hover:underline text-black {isStudent &&
|
||||||
!lesson.is_complete
|
!lesson.is_unlocked
|
||||||
? 'cursor-not-allowed'
|
? 'cursor-not-allowed'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import CourseContainer from '$lib/components/CourseContainer/index.svelte';
|
import CourseContainer from '$lib/components/CourseContainer/index.svelte';
|
||||||
import { fetchCourse, fetchLesson } from '$lib/utils/services/courses';
|
import { fetchCourse, fetchLesson } from '$lib/utils/services/courses';
|
||||||
|
import CheckmarkOutlineIcon from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
||||||
import PageNav from '$lib/components/PageNav/index.svelte';
|
import PageNav from '$lib/components/PageNav/index.svelte';
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte';
|
import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte';
|
||||||
import MODES from '$lib/utils/constants/mode.js';
|
import MODES from '$lib/utils/constants/mode.js';
|
||||||
import { setCourse, course } from '$lib/components/Course/store';
|
import { setCourse, course } from '$lib/components/Course/store';
|
||||||
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
|
import { lesson, handleSaveLesson } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
let prevMode = '';
|
let prevMode = '';
|
||||||
let prevLessonId = '';
|
let prevLessonId = '';
|
||||||
let isFetching = false;
|
let isFetching = false;
|
||||||
|
let isMarkingComplete = false;
|
||||||
|
|
||||||
async function fetchReqData(lessonId = '') {
|
async function fetchReqData(lessonId = '') {
|
||||||
isFetching = true;
|
isFetching = true;
|
||||||
@@ -41,6 +43,13 @@
|
|||||||
isFetching = false;
|
isFetching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function markLessonComplete() {
|
||||||
|
isMarkingComplete = true;
|
||||||
|
$lesson.is_complete = !$lesson.is_complete;
|
||||||
|
await handleSaveLesson($lesson, $course.id);
|
||||||
|
isMarkingComplete = false;
|
||||||
|
}
|
||||||
|
|
||||||
function setLesson(lessonData, totalExercises) {
|
function setLesson(lessonData, totalExercises) {
|
||||||
if (!lessonData) return;
|
if (!lessonData) return;
|
||||||
|
|
||||||
@@ -99,6 +108,18 @@
|
|||||||
{:else if !!data.lessonId}
|
{:else if !!data.lessonId}
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<Materials lessonId={data.lessonId} {mode} {prevMode} />
|
<Materials lessonId={data.lessonId} {mode} {prevMode} />
|
||||||
|
|
||||||
|
<div class="w-full flex flex-row-reverse">
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={markLessonComplete}
|
||||||
|
isLoading={isMarkingComplete}
|
||||||
|
isDisabled={isMarkingComplete}
|
||||||
|
className="mt-10"
|
||||||
|
>
|
||||||
|
<CheckmarkOutlineIcon size={24} class="carbon-icon mr-2" />
|
||||||
|
Mark as {$lesson.is_complete ? 'Incomplete' : 'Complete'}
|
||||||
|
</PrimaryButton>
|
||||||
|
</div>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
{/if}
|
{/if}
|
||||||
</CourseContainer>
|
</CourseContainer>
|
||||||
|
|||||||
@@ -3,23 +3,17 @@
|
|||||||
import { InlineCalendar, Datepicker } from 'svelte-calendar';
|
import { InlineCalendar, Datepicker } from 'svelte-calendar';
|
||||||
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
||||||
|
|
||||||
import {
|
import { formatUserUpcomingData, formatDate } from '$lib/utils/functions/routes/dashboard';
|
||||||
formatUserUpcomingData,
|
|
||||||
formatDate,
|
|
||||||
} from '$lib/utils/functions/routes/dashboard';
|
|
||||||
import { user } from '$lib/utils/store/user';
|
import { user } from '$lib/utils/store/user';
|
||||||
import { fetchUserUpcomingData } from '$lib/utils/services/dashboard';
|
import { fetchUserUpcomingData } from '$lib/utils/services/dashboard';
|
||||||
import { isMobile } from '$lib/utils/store/useMobile';
|
import { isMobile } from '$lib/utils/store/useMobile';
|
||||||
import type {
|
import type { UserLessonDataType, LessonsByMonthIndexType } from '$lib/utils/types';
|
||||||
UserLessonDataType,
|
|
||||||
LessonsByMonthIndexType,
|
|
||||||
} from '$lib/utils/types';
|
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
calendar: {
|
calendar: {
|
||||||
width: '400px',
|
width: '400px',
|
||||||
shadow: '0px 0px 5px rgba(0, 0, 0, 0.25)',
|
shadow: '0px 0px 5px rgba(0, 0, 0, 0.25)'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let store;
|
let store;
|
||||||
@@ -37,8 +31,7 @@
|
|||||||
// );
|
// );
|
||||||
|
|
||||||
const renderedMonths =
|
const renderedMonths =
|
||||||
calendar?.querySelectorAll('.contents .container .stage .grid .grid') ||
|
calendar?.querySelectorAll('.contents .container .stage .grid .grid') || [];
|
||||||
[];
|
|
||||||
// console.log(`renderedMonths`, renderedMonths);
|
// console.log(`renderedMonths`, renderedMonths);
|
||||||
const currentMonthIndex = new Date($store?.selected).getMonth();
|
const currentMonthIndex = new Date($store?.selected).getMonth();
|
||||||
const currentMonth = renderedMonths[currentMonthIndexInRenderedMonth];
|
const currentMonth = renderedMonths[currentMonthIndexInRenderedMonth];
|
||||||
@@ -63,10 +56,7 @@
|
|||||||
if (!!currentMonthData[dateIndex]) {
|
if (!!currentMonthData[dateIndex]) {
|
||||||
// console.log(`dateIndex`, dateIndex);
|
// console.log(`dateIndex`, dateIndex);
|
||||||
// console.log(`lesson data`, currentMonthData[dateIndex]);
|
// console.log(`lesson data`, currentMonthData[dateIndex]);
|
||||||
dayElement.insertAdjacentHTML(
|
dayElement.insertAdjacentHTML('beforeend', '<span class="active-day">•</span>');
|
||||||
'beforeend',
|
|
||||||
'<span class="active-day">•</span>'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -84,9 +74,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataOfSelectedDate(
|
function getDataOfSelectedDate(_selectedDate: string): UserLessonDataType[] | [] {
|
||||||
_selectedDate: string
|
|
||||||
): UserLessonDataType[] | [] {
|
|
||||||
if (!_selectedDate) return [];
|
if (!_selectedDate) return [];
|
||||||
|
|
||||||
const selectedDate = new Date(_selectedDate).toUTCString();
|
const selectedDate = new Date(_selectedDate).toUTCString();
|
||||||
@@ -97,8 +85,7 @@
|
|||||||
// If we changed the month, update the calendar dots
|
// If we changed the month, update the calendar dots
|
||||||
const prevMonthIndex = new Date(prevSelectedDate).getMonth();
|
const prevMonthIndex = new Date(prevSelectedDate).getMonth();
|
||||||
if (prevMonthIndex !== monthIndex) {
|
if (prevMonthIndex !== monthIndex) {
|
||||||
const currentMonthIndexInRenderedMonth =
|
const currentMonthIndexInRenderedMonth = monthIndex < prevMonthIndex ? 1 : 2;
|
||||||
monthIndex < prevMonthIndex ? 1 : 2;
|
|
||||||
|
|
||||||
addDotsToCalendar(currentMonthIndexInRenderedMonth);
|
addDotsToCalendar(currentMonthIndexInRenderedMonth);
|
||||||
}
|
}
|
||||||
@@ -163,9 +150,7 @@
|
|||||||
</Datepicker>
|
</Datepicker>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div class="header text-lg font-bold text-blue-700 flex px-5 items-center">
|
||||||
class="header text-lg font-bold text-blue-700 flex px-5 items-center"
|
|
||||||
>
|
|
||||||
{formatDate($store?.selected)}
|
{formatDate($store?.selected)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -176,11 +161,11 @@
|
|||||||
<a
|
<a
|
||||||
class="text-black-700 text-lg font-bold flex items-center"
|
class="text-black-700 text-lg font-bold flex items-center"
|
||||||
href={`/courses/${lessonData.course_id}/lessons/${
|
href={`/courses/${lessonData.course_id}/lessons/${
|
||||||
lessonData.is_complete ? lessonData.lesson_id : ''
|
lessonData.is_unlocked ? lessonData.lesson_id : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{lessonData.lesson_title}
|
{lessonData.lesson_title}
|
||||||
{#if lessonData.is_complete}
|
{#if lessonData.is_unlocked}
|
||||||
<span class="ml-2 success">
|
<span class="ml-2 success">
|
||||||
<CheckmarkFilledIcon size={20} class="carbon-icon" />
|
<CheckmarkFilledIcon size={20} class="carbon-icon" />
|
||||||
</span>
|
</span>
|
||||||
@@ -189,15 +174,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
<p class="dark:text-white text-grey text-sm flex items-center">
|
<p class="dark:text-white text-grey text-sm flex items-center">
|
||||||
<a
|
<a class="underline text-blue-700 my-2" href="/courses/{lessonData.course_id}">
|
||||||
class="underline text-blue-700 my-2"
|
|
||||||
href="/courses/{lessonData.course_id}"
|
|
||||||
>
|
|
||||||
{` ${lessonData.course_title}`}
|
{` ${lessonData.course_title}`}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if lessonData.is_complete}
|
{#if lessonData.is_unlocked}
|
||||||
<a
|
<a
|
||||||
class="join-call rounded-3xl bg-blue-600 text-white py-3 font-bold shadow-lg {!lessonData.call_url &&
|
class="join-call rounded-3xl bg-blue-600 text-white py-3 font-bold shadow-lg {!lessonData.call_url &&
|
||||||
'opacity-50 pointer-events-none cursor-not-allowed'}"
|
'opacity-50 pointer-events-none cursor-not-allowed'}"
|
||||||
@@ -212,9 +194,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p
|
<p class="dark:text-white flex items-center justify-center w-full no-data">
|
||||||
class="dark:text-white flex items-center justify-center w-full no-data"
|
|
||||||
>
|
|
||||||
No lesson on this day
|
No lesson on this day
|
||||||
</p>
|
</p>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -3,20 +3,15 @@
|
|||||||
// import { supabase } from '$lib/utils/functions/supabase';
|
// import { supabase } from '$lib/utils/functions/supabase';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
|
import UnlockedIcon from 'carbon-icons-svelte/lib/Unlocked.svelte';
|
||||||
|
import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte';
|
||||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||||
import { InlineCalendar, Datepicker } from 'svelte-calendar';
|
import { InlineCalendar, Datepicker } from 'svelte-calendar';
|
||||||
import CheckmarkFilledIcon from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
import { formatUserUpcomingData, formatDate } from '$lib/utils/functions/routes/dashboard';
|
||||||
import {
|
|
||||||
formatUserUpcomingData,
|
|
||||||
formatDate,
|
|
||||||
} from '$lib/utils/functions/routes/dashboard';
|
|
||||||
import { user } from '$lib/utils/store/user';
|
import { user } from '$lib/utils/store/user';
|
||||||
import { fetchUserUpcomingData } from '$lib/utils/services/dashboard';
|
import { fetchUserUpcomingData } from '$lib/utils/services/dashboard';
|
||||||
import { isMobile } from '$lib/utils/store/useMobile';
|
import { isMobile } from '$lib/utils/store/useMobile';
|
||||||
import type {
|
import type { UserLessonDataType, LessonsByMonthIndexType } from '$lib/utils/types';
|
||||||
UserLessonDataType,
|
|
||||||
LessonsByMonthIndexType,
|
|
||||||
} from '$lib/utils/types';
|
|
||||||
import WelcomeModal from '$lib/components/WelcomeModal/WelcomeModal.svelte';
|
import WelcomeModal from '$lib/components/WelcomeModal/WelcomeModal.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
@@ -31,8 +26,8 @@
|
|||||||
calendar: {
|
calendar: {
|
||||||
width: '300px',
|
width: '300px',
|
||||||
height: '300px',
|
height: '300px',
|
||||||
shadow: '0px 0px 5px rgba(0, 0, 0, 0.25)',
|
shadow: '0px 0px 5px rgba(0, 0, 0, 0.25)'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let store;
|
let store;
|
||||||
@@ -44,20 +39,20 @@
|
|||||||
const boxes = [
|
const boxes = [
|
||||||
{
|
{
|
||||||
label: 'Revenue (NGN)',
|
label: 'Revenue (NGN)',
|
||||||
value: '0.00',
|
value: '0.00'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Number of sales',
|
label: 'Number of sales',
|
||||||
value: 0,
|
value: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Number of courses',
|
label: 'Number of courses',
|
||||||
value: 0,
|
value: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Total students',
|
label: 'Total students',
|
||||||
value: 0,
|
value: 0
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
const activities = [
|
const activities = [
|
||||||
{
|
{
|
||||||
@@ -66,7 +61,7 @@
|
|||||||
name: 'Nnancy Okoye',
|
name: 'Nnancy Okoye',
|
||||||
time: '1 hour ago',
|
time: '1 hour ago',
|
||||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||||
link: '/',
|
link: '/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
avatar:
|
avatar:
|
||||||
@@ -74,7 +69,7 @@
|
|||||||
name: 'Bro Shagi',
|
name: 'Bro Shagi',
|
||||||
time: '2 days ago',
|
time: '2 days ago',
|
||||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||||
link: '/',
|
link: '/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
avatar:
|
avatar:
|
||||||
@@ -82,7 +77,7 @@
|
|||||||
name: 'Bill Gates',
|
name: 'Bill Gates',
|
||||||
time: '13 hours ago',
|
time: '13 hours ago',
|
||||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||||
link: '/',
|
link: '/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
avatar:
|
avatar:
|
||||||
@@ -90,8 +85,8 @@
|
|||||||
name: 'Steve Jobs',
|
name: 'Steve Jobs',
|
||||||
time: 'yesterday',
|
time: 'yesterday',
|
||||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||||
link: '/',
|
link: '/'
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const addDotsToCalendar = (currentMonthIndexInRenderedMonth: number) =>
|
const addDotsToCalendar = (currentMonthIndexInRenderedMonth: number) =>
|
||||||
@@ -103,8 +98,7 @@
|
|||||||
// );
|
// );
|
||||||
|
|
||||||
const renderedMonths =
|
const renderedMonths =
|
||||||
calendar?.querySelectorAll('.contents .container .stage .grid .grid') ||
|
calendar?.querySelectorAll('.contents .container .stage .grid .grid') || [];
|
||||||
[];
|
|
||||||
// console.log(`renderedMonths`, renderedMonths);
|
// console.log(`renderedMonths`, renderedMonths);
|
||||||
const currentMonthIndex = new Date($store?.selected).getMonth();
|
const currentMonthIndex = new Date($store?.selected).getMonth();
|
||||||
const currentMonth = renderedMonths[currentMonthIndexInRenderedMonth];
|
const currentMonth = renderedMonths[currentMonthIndexInRenderedMonth];
|
||||||
@@ -129,10 +123,7 @@
|
|||||||
if (!!currentMonthData[dateIndex]) {
|
if (!!currentMonthData[dateIndex]) {
|
||||||
// console.log(`dateIndex`, dateIndex);
|
// console.log(`dateIndex`, dateIndex);
|
||||||
// console.log(`lesson data`, currentMonthData[dateIndex]);
|
// console.log(`lesson data`, currentMonthData[dateIndex]);
|
||||||
dayElement.insertAdjacentHTML(
|
dayElement.insertAdjacentHTML('beforeend', '<span class="active-day">•</span>');
|
||||||
'beforeend',
|
|
||||||
'<span class="active-day">•</span>'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -150,9 +141,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataOfSelectedDate(
|
function getDataOfSelectedDate(_selectedDate: string): UserLessonDataType[] | [] {
|
||||||
_selectedDate: string
|
|
||||||
): UserLessonDataType[] | [] {
|
|
||||||
if (!_selectedDate) return [];
|
if (!_selectedDate) return [];
|
||||||
|
|
||||||
const selectedDate = new Date(_selectedDate).toUTCString();
|
const selectedDate = new Date(_selectedDate).toUTCString();
|
||||||
@@ -163,8 +152,7 @@
|
|||||||
// If we changed the month, update the calendar dots
|
// If we changed the month, update the calendar dots
|
||||||
const prevMonthIndex = new Date(prevSelectedDate).getMonth();
|
const prevMonthIndex = new Date(prevSelectedDate).getMonth();
|
||||||
if (prevMonthIndex !== monthIndex) {
|
if (prevMonthIndex !== monthIndex) {
|
||||||
const currentMonthIndexInRenderedMonth =
|
const currentMonthIndexInRenderedMonth = monthIndex < prevMonthIndex ? 1 : 2;
|
||||||
monthIndex < prevMonthIndex ? 1 : 2;
|
|
||||||
|
|
||||||
addDotsToCalendar(currentMonthIndexInRenderedMonth);
|
addDotsToCalendar(currentMonthIndexInRenderedMonth);
|
||||||
}
|
}
|
||||||
@@ -212,9 +200,7 @@
|
|||||||
|
|
||||||
<div class="flex items-start flex-wrap">
|
<div class="flex items-start flex-wrap">
|
||||||
{#each boxes as box}
|
{#each boxes as box}
|
||||||
<div
|
<div class="box flex flex-col rounded border border-gray-200 justify-center px-5 mr-5 mb-5">
|
||||||
class="box flex flex-col rounded border border-gray-200 justify-center px-5 mr-5 mb-5"
|
|
||||||
>
|
|
||||||
<p class="dark:text-white mb-2 text-sm">{box.label}</p>
|
<p class="dark:text-white mb-2 text-sm">{box.label}</p>
|
||||||
<p class="dark:text-white text-xl font-bold">{box.value}</p>
|
<p class="dark:text-white text-xl font-bold">{box.value}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,28 +245,25 @@
|
|||||||
<a
|
<a
|
||||||
class="text-black-700 text-lg font-bold flex items-center"
|
class="text-black-700 text-lg font-bold flex items-center"
|
||||||
href={`/courses/${lessonData.course_id}/lessons/${
|
href={`/courses/${lessonData.course_id}/lessons/${
|
||||||
lessonData.is_complete ? lessonData.lesson_id : ''
|
lessonData.is_unlocked ? lessonData.lesson_id : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{lessonData.lesson_title}
|
{lessonData.lesson_title}
|
||||||
{#if lessonData.is_complete}
|
{#if lessonData.is_unlocked}
|
||||||
<span class="ml-2 success">
|
<span class="ml-2 success">
|
||||||
<CheckmarkFilledIcon size={20} class="carbon-icon" />
|
<LockedIcon class="carbon-icon" />
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-md ml-2">🔒</span>
|
<UnlockedIcon class="carbon-icon" />
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
<p class="dark:text-white text-grey text-sm flex items-center">
|
<p class="dark:text-white text-grey text-sm flex items-center">
|
||||||
<a
|
<a class="underline text-blue-700 my-2" href="/courses/{lessonData.course_id}">
|
||||||
class="underline text-blue-700 my-2"
|
|
||||||
href="/courses/{lessonData.course_id}"
|
|
||||||
>
|
|
||||||
{` ${lessonData.course_title}`}
|
{` ${lessonData.course_title}`}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if lessonData.is_complete}
|
{#if lessonData.is_unlocked}
|
||||||
<a
|
<a
|
||||||
class="join-call rounded-3xl bg-blue-600 text-white py-3 font-bold shadow-lg {!lessonData.call_url &&
|
class="join-call rounded-3xl bg-blue-600 text-white py-3 font-bold shadow-lg {!lessonData.call_url &&
|
||||||
'opacity-50 pointer-events-none cursor-not-allowed'}"
|
'opacity-50 pointer-events-none cursor-not-allowed'}"
|
||||||
@@ -295,9 +278,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p
|
<p class="dark:text-white flex items-center justify-center w-full no-data">
|
||||||
class="dark:text-white flex items-center justify-center w-full no-data"
|
|
||||||
>
|
|
||||||
No lesson on this day
|
No lesson on this day
|
||||||
</p>
|
</p>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user