feat: course types - self paced and live class (#309)
* working on the course type * trying to mark it persist * almost done here * fixed sendig emtpy options bug * created migration file * created migration file * made updates based on PR review * merge * update course type card * add redirect logic with classroomio.com/new * make new course modal only open/close with url * feat: page active state (#315) * finished the active link state * updated the active link state based on review * simplify code --------- Co-authored-by: Rotimi Best <[email protected]> * add type to course card --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
@@ -225,10 +225,10 @@
|
||||
}
|
||||
|
||||
function getPageRoles(org: CurrentOrg) {
|
||||
const roles = [1, 2];
|
||||
const roles: number[] = [1, 2];
|
||||
|
||||
if (org.customization.course.newsfeed) {
|
||||
roles.push(3);
|
||||
roles.concat([3]);
|
||||
}
|
||||
|
||||
return roles;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
} from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import PageBody from '$lib/components/PageBody/index.svelte';
|
||||
|
||||
import AddLessonModal from '$lib/components/Course/components/Lesson/AddLessonModal.svelte';
|
||||
import NewLessonModal from '$lib/components/Course/components/Lesson/NewLessonModal.svelte';
|
||||
import DeleteLessonConfirmation from '$lib/components/Course/components/Lesson/DeleteLessonConfirmation.svelte';
|
||||
import { handleAddLessonWidget } from '$lib/components/Course/components/Navigation/store';
|
||||
import { Video, Calendar } from 'carbon-icons-svelte';
|
||||
@@ -28,6 +28,7 @@
|
||||
import type { Course, Lesson } from '$lib/utils/types';
|
||||
import { t } from '$lib/utils/functions/translations.js';
|
||||
import { goto } from '$app/navigation';
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -126,7 +127,7 @@
|
||||
</script>
|
||||
|
||||
{#if $handleAddLessonWidget}
|
||||
<AddLessonModal {isStudent} />
|
||||
<NewLessonModal {isStudent} />
|
||||
{/if}
|
||||
|
||||
<DeleteLessonConfirmation
|
||||
@@ -217,84 +218,86 @@
|
||||
{/if}
|
||||
|
||||
<!-- Lesson Educator -->
|
||||
<div
|
||||
class="mt-2 flex w-4/5 flex-col items-start justify-between lg:flex-row lg:items-center"
|
||||
>
|
||||
<div class="lg:mb-0">
|
||||
{#if lessonEditing === lesson.id}
|
||||
<Select
|
||||
bind:value={lesson.profile}
|
||||
options={$group.tutors}
|
||||
labelKey="fullname"
|
||||
className="sm:my-1 w-[100%]"
|
||||
/>
|
||||
{:else if !lesson.profile}
|
||||
<p class="dark:text-white ml-2 text-sm mb-3">
|
||||
{$t('course.navItem.lessons.no_tutor')}
|
||||
</p>
|
||||
{:else}
|
||||
<a href="." class="mb-2 flex items-center hover:underline">
|
||||
<Avatar
|
||||
className="w-6 h-6"
|
||||
src={lesson.profile.avatar_url}
|
||||
name="Avatar placeholder"
|
||||
{#if $course.type === COURSE_TYPE.LIVE_CLASS}
|
||||
<div
|
||||
class="mt-2 flex w-4/5 flex-col items-start justify-between lg:flex-row lg:items-center"
|
||||
>
|
||||
<div class="lg:mb-0">
|
||||
{#if lessonEditing === lesson.id}
|
||||
<Select
|
||||
bind:value={lesson.profile}
|
||||
options={$group.tutors}
|
||||
labelKey="fullname"
|
||||
className="sm:my-1 w-[100%]"
|
||||
/>
|
||||
<p class="ml-2 text-sm dark:text-white">
|
||||
{lesson.profile.fullname}
|
||||
{:else if !lesson.profile}
|
||||
<p class="dark:text-white ml-2 text-sm mb-3">
|
||||
{$t('course.navItem.lessons.no_tutor')}
|
||||
</p>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Lesson Date -->
|
||||
<div class="flex items-center lg:mb-0">
|
||||
{#if lessonEditing === lesson.id}
|
||||
<DateField
|
||||
value={formatDate(lesson.lesson_at)}
|
||||
className="p-2 my-2 rounded-md sm:w-[179px] dark:bg-neutral-800 dark:text-white"
|
||||
onChange={(e) => (lesson.lesson_at = e.target.value)}
|
||||
/>
|
||||
{:else}
|
||||
<div class="mb-2 flex">
|
||||
<Calendar size={20} class="carbon-icon text-gray-400 dark:text-white" />
|
||||
<p class="ml-2 text-sm dark:text-white">
|
||||
{new Date(lesson.lesson_at || '').toDateString()}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Lesson Call url -->
|
||||
<div class="mb-3 flex items-center lg:mb-0">
|
||||
{#if lessonEditing === lesson.id}
|
||||
<TextField
|
||||
bind:value={lesson.call_url}
|
||||
autoFocus={true}
|
||||
className="w-[179px]"
|
||||
placeholder="https://meet.google.com/mga-dsjs-fmb"
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex">
|
||||
<Video size={20} class="carbon-icon ml-0.5 text-gray-400 dark:text-white" />
|
||||
<a
|
||||
class="text-primary-600 ml-2 text-sm"
|
||||
href={lesson.call_url || '#'}
|
||||
target="_blank"
|
||||
>
|
||||
{lesson.call_url
|
||||
? $t('course.navItem.lessons.join_lesson')
|
||||
: $t('course.navItem.lessons.no_link')}
|
||||
{:else}
|
||||
<a href="." class="mb-2 flex items-center hover:underline">
|
||||
<Avatar
|
||||
className="w-6 h-6"
|
||||
src={lesson.profile.avatar_url}
|
||||
name="Avatar placeholder"
|
||||
/>
|
||||
<p class="ml-2 text-sm dark:text-white">
|
||||
{lesson.profile.fullname}
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Lesson Date -->
|
||||
<div class="flex items-center lg:mb-0">
|
||||
{#if lessonEditing === lesson.id}
|
||||
<DateField
|
||||
value={formatDate(lesson.lesson_at)}
|
||||
className="p-2 my-2 rounded-md sm:w-[179px] dark:bg-neutral-800 dark:text-white"
|
||||
onChange={(e) => (lesson.lesson_at = e.target.value)}
|
||||
/>
|
||||
{:else}
|
||||
<div class="mb-2 flex">
|
||||
<Calendar size={20} class="carbon-icon text-gray-400 dark:text-white" />
|
||||
<p class="ml-2 text-sm dark:text-white">
|
||||
{new Date(lesson.lesson_at || '').toDateString()}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Lesson Call url -->
|
||||
<div class="mb-3 flex items-center lg:mb-0">
|
||||
{#if lessonEditing === lesson.id}
|
||||
<TextField
|
||||
bind:value={lesson.call_url}
|
||||
autoFocus={true}
|
||||
className="w-[179px]"
|
||||
placeholder="https://meet.google.com/mga-dsjs-fmb"
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex">
|
||||
<Video size={20} class="carbon-icon ml-0.5 text-gray-400 dark:text-white" />
|
||||
<a
|
||||
class="text-primary-600 ml-2 text-sm"
|
||||
href={lesson.call_url || '#'}
|
||||
target="_blank"
|
||||
>
|
||||
{lesson.call_url
|
||||
? $t('course.navItem.lessons.join_lesson')
|
||||
: $t('course.navItem.lessons.no_link')}
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- 3 dot edit -->
|
||||
<div class="flex flex-row">
|
||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||
<OverflowMenu size="xl" class="absolute right-0 top-0">
|
||||
<OverflowMenu size="xl" class={`absolute right-0 top-0`}>
|
||||
<OverflowMenuItem
|
||||
disabled={isStudent}
|
||||
text={lesson.is_unlocked
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO WRITE A REDIRECT FUNCTION FOR THIS THIS PAGE
|
||||
|
||||
function getPageRoles(org: CurrentOrg) {
|
||||
const roles = [1, 2];
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
const questionAnswer = questionAnswers.find(
|
||||
(answer: { question_id: string }) => answer.question_id == questionId
|
||||
);
|
||||
|
||||
console.log('this is the question answer in the admin side', questionAnswer);
|
||||
const point = questionAnswerByPoint[questionId];
|
||||
|
||||
totalPoints += parseInt(point, 10);
|
||||
|
||||
@@ -6,24 +6,18 @@
|
||||
import { fetchCourses } from '$lib/components/Courses/api';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { courses, courseMetaDeta, coursesComplete } from '$lib/components/Courses/store';
|
||||
import {
|
||||
courses,
|
||||
courseMetaDeta,
|
||||
coursesComplete,
|
||||
coursesInProgress
|
||||
} from '$lib/components/Courses/store';
|
||||
import { browser } from '$app/environment';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: $t('my_learning.progress'),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: $t('my_learning.complete'),
|
||||
value: 2
|
||||
}
|
||||
];
|
||||
let currentTab = tabs[0].value;
|
||||
let hasFetched = false;
|
||||
|
||||
function onChange(tab: 0) {
|
||||
function onChange(tab) {
|
||||
return () => (currentTab = tab);
|
||||
}
|
||||
|
||||
@@ -51,6 +45,18 @@
|
||||
$: if (browser && $profile.id && $currentOrg.id) {
|
||||
getCourses($profile.id, $currentOrg.id);
|
||||
}
|
||||
|
||||
$: tabs = [
|
||||
{
|
||||
label: `${$t('my_learning.progress')} (${$coursesInProgress.length})`,
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: `${$t('my_learning.complete')} (${$coursesComplete.length})`,
|
||||
value: '2'
|
||||
}
|
||||
];
|
||||
$: currentTab = tabs[0].value;
|
||||
</script>
|
||||
|
||||
<section class="max-w-6xl mx-auto">
|
||||
@@ -63,7 +69,7 @@
|
||||
<slot:fragment slot="content">
|
||||
<TabContent value={tabs[0].value} index={currentTab}>
|
||||
<Courses
|
||||
courses={$courses}
|
||||
courses={$coursesInProgress}
|
||||
emptyTitle={$t('my_learning.not_in_progress')}
|
||||
emptyDescription={$t('my_learning.any_progress')}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
export let data;
|
||||
|
||||
$: if ($currentOrg.id && data.orgName === '*') {
|
||||
goto(`/org/${$currentOrg.siteName}`);
|
||||
const newUrl = $page.url.pathname.replace('*', $currentOrg.siteName);
|
||||
goto(newUrl + $page.url.search);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Search, Dropdown } from 'carbon-components-svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { fetchCourses } from '$lib/components/Courses/api';
|
||||
import Courses from '$lib/components/Courses/index.svelte';
|
||||
import NewCourseModal from '$lib/components/Courses/components/NewCourseModal/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { courses, createCourseModal, courseMetaDeta } from '$lib/components/Courses/store';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { courses, courseMetaDeta } from '$lib/components/Courses/store';
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import { Add } from 'carbon-icons-svelte';
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { isOrgAdmin } from '$lib/utils/store/org';
|
||||
@@ -19,6 +18,7 @@
|
||||
import Grid from 'carbon-icons-svelte/lib/Grid.svelte';
|
||||
import List from 'carbon-icons-svelte/lib/List.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -28,12 +28,6 @@
|
||||
let filteredCourses: Course[];
|
||||
let hasFetched = false;
|
||||
|
||||
const urlParams = new URLSearchParams($page.url.search);
|
||||
|
||||
if (urlParams.get('create') === 'true') {
|
||||
$createCourseModal.open = true;
|
||||
}
|
||||
|
||||
async function getCourses(userId: string | undefined, orgId: string) {
|
||||
if (cantFetch && typeof cantFetch === 'boolean' && orgId && !hasFetched) {
|
||||
// only show is loading when fetching for the first time
|
||||
@@ -86,6 +80,10 @@
|
||||
localStorage.setItem('courseView', preference);
|
||||
};
|
||||
|
||||
const openNewCourseModal = () => {
|
||||
goto($currentOrgPath + '/courses?create=true');
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const courseView = localStorage.getItem('courseView') as 'grid' | 'list' | null;
|
||||
|
||||
@@ -107,7 +105,7 @@
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<h1 class="dark:text-white text-2xl md:text-3xl font-bold">{$t('courses.heading')}</h1>
|
||||
{#if $isMobile}
|
||||
<PrimaryButton isDisabled={!$isOrgAdmin} onClick={() => ($createCourseModal.open = true)}>
|
||||
<PrimaryButton isDisabled={!$isOrgAdmin} onClick={openNewCourseModal}>
|
||||
<Add size={24} />
|
||||
</PrimaryButton>
|
||||
{:else}
|
||||
@@ -115,7 +113,7 @@
|
||||
label={$t('courses.heading_button')}
|
||||
variant={VARIANTS.CONTAINED_DARK}
|
||||
isDisabled={!$isOrgAdmin}
|
||||
onClick={() => ($createCourseModal.open = true)}
|
||||
onClick={openNewCourseModal}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user