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 <best@classroomio.com> * add type to course card --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
@@ -112,11 +112,9 @@
|
||||
disabled={$isFreePlan}
|
||||
>
|
||||
<span slot="labelA" style={$globalStore.isDark ? 'color: white' : 'color: #161616'}
|
||||
>{$t('course.navItem.certificates.locked')}</span
|
||||
>
|
||||
<span slot="labelB" style="color: green"
|
||||
>{$t('course.navItem.certificates.unlocked')}</span
|
||||
>{$t('generic.locked')}</span
|
||||
>
|
||||
<span slot="labelB" style="color: green">{$t('generic.unlocked')}</span>
|
||||
</Toggle>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
<script>
|
||||
import UnlockedIcon from 'carbon-icons-svelte/lib/Unlocked.svelte';
|
||||
import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
import Select from '$lib/components/Form/Select.svelte';
|
||||
import {
|
||||
lessons,
|
||||
handleSaveLesson
|
||||
} from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import { course, group } from '$lib/components/Course/store';
|
||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { handleAddLessonWidget } from '../Navigation/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
export let isStudent = false;
|
||||
let errors = {
|
||||
title: ''
|
||||
};
|
||||
let lesson = {
|
||||
id: null,
|
||||
title: '',
|
||||
profile: undefined,
|
||||
call_url: undefined,
|
||||
lesson_at: new Date(),
|
||||
is_unlocked: false
|
||||
};
|
||||
|
||||
function getLessonOrder(id = '', fallbackIndex) {
|
||||
const index = fallbackIndex || $lessons.findIndex((lesson) => lesson.id === id);
|
||||
if (index < 9) {
|
||||
return '0' + (index + 1);
|
||||
} else {
|
||||
return index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (lesson.title.trim() === '') {
|
||||
errors.title = 'title cannot be empty';
|
||||
return;
|
||||
}
|
||||
const savedLesson = await handleSaveLesson(lesson, $course.id);
|
||||
if (Array.isArray(savedLesson) && savedLesson[0]) {
|
||||
const newLesson = savedLesson[0];
|
||||
lesson.id = newLesson.id;
|
||||
$lessons = [...$lessons, lesson];
|
||||
goto('/courses/' + $course.id + '/lessons/' + lesson.id);
|
||||
}
|
||||
$handleAddLessonWidget.open = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
onClose={() => ($handleAddLessonWidget.open = false)}
|
||||
bind:open={$handleAddLessonWidget.open}
|
||||
width="w-[80%] md:w-[65%]"
|
||||
containerClass="overflow-hidden"
|
||||
modalHeading={$t('course.navItem.lessons.add_lesson.modal_heading')}
|
||||
>
|
||||
<div
|
||||
class="sm:min-h-[245px] md:min-h-[100px] lg:min-h-[190px] relative m-auto rounded-md border-2 border-gray-200 dark:border-neutral-600 py-2 md:py-3 px-2 md:px-5 mb-2 md:mb-4 flex flex-wrap items-center dark:bg-neutral-800"
|
||||
>
|
||||
<div class="mr-5">
|
||||
<TextChip
|
||||
value={getLessonOrder('', $lessons.length)}
|
||||
size="sm"
|
||||
shape="rounded-full"
|
||||
className="bg-primary-200 text-primary-600 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-4/5">
|
||||
<div class="flex flex-row gap-1 md:gap-2">
|
||||
<TextField
|
||||
label={$t('course.navItem.lessons.add_lesson.lesson_title')}
|
||||
bind:value={lesson.title}
|
||||
autofocus={true}
|
||||
className="flex-1 min-w-lg max-w-lg"
|
||||
isRequired={true}
|
||||
errorMessage={errors.title}
|
||||
/>
|
||||
<IconButton
|
||||
disabled={isStudent}
|
||||
toolTipProps={isStudent
|
||||
? {}
|
||||
: {
|
||||
title: `${$t('course.navItem.lessons.add_lesson.click_to')} ${
|
||||
lesson.is_unlocked
|
||||
? $t('course.navItem.lessons.add_lesson.lock')
|
||||
: $t('course.navItem.lessons.add_lesson.unlock')
|
||||
}`,
|
||||
direction: 'right'
|
||||
}}
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
lesson.is_unlocked = !lesson.is_unlocked;
|
||||
handleSaveLesson(lesson, $course.id);
|
||||
}}
|
||||
class="p-1"
|
||||
>
|
||||
{#if lesson.is_unlocked}
|
||||
<UnlockedIcon size={24} class="carbon-icon dark:text-white" />
|
||||
{:else}
|
||||
<LockedIcon size={24} class="carbon-icon dark:text-white" />
|
||||
{/if}
|
||||
</button>
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start justify-between flex-col lg:flex-row lg:items-center mt-2 w-4/5">
|
||||
<div class="lg:mb-0">
|
||||
<Select
|
||||
bind:value={lesson.profile}
|
||||
options={$group.tutors}
|
||||
labelKey="fullname"
|
||||
className="sm:my-1 w-[100%]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center lg:mb-0">
|
||||
<input
|
||||
type="date"
|
||||
name="lesson-date-picker"
|
||||
class="p-2 my-2 rounded-md sm:w-[179px] dark:bg-neutral-800 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-3 lg:mb-0">
|
||||
<TextField className="w-[179px]" placeholder="https://meet.google.com/mga-dsjs-fmb" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PrimaryButton label={$t('course.navItem.lessons.add_lesson.save')} onClick={handleSave} />
|
||||
</Modal>
|
||||
+4
-4
@@ -19,18 +19,18 @@
|
||||
onClose={() => ($deleteConfirmation.open = false)}
|
||||
bind:open={$deleteConfirmation.open}
|
||||
width="w-96"
|
||||
modalHeading={$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation')}
|
||||
modalHeading={$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.title')}
|
||||
>
|
||||
<div>
|
||||
<h1 class="dark:text-white text-lg">
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.sure')}
|
||||
{$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.sure')}
|
||||
</h1>
|
||||
|
||||
<div class="mt-5 flex items-center justify-between">
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label={$t('course.navItem.lessons.exercises.all_exercises.no')}
|
||||
label={$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.no')}
|
||||
onClick={() => {
|
||||
$deleteConfirmation.open = false;
|
||||
onCancel();
|
||||
@@ -39,7 +39,7 @@
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
variant={VARIANTS.CONTAINED}
|
||||
label={$t('course.navItem.lessons.exercises.all_exercises.yes')}
|
||||
label={$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.yes')}
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
$deleteConfirmation.open = false;
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
import { SkeletonText } from 'carbon-components-svelte';
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<SkeletonText width="100%" heading />
|
||||
<div class="space-y-3">
|
||||
<SkeletonText width="100%" heading />
|
||||
<SkeletonText width="100%" heading />
|
||||
<SkeletonText width="100%" heading />
|
||||
<SkeletonText width="100%" heading />
|
||||
</div>
|
||||
</div>
|
||||
+115
-13
@@ -17,7 +17,11 @@
|
||||
import { getPropsForQuestion, filterOutDeleted } from './functions';
|
||||
import { formatAnswers, getGroupMemberId } from '$lib/components/Course/function';
|
||||
import { submitExercise } from '$lib/utils/services/courses';
|
||||
import { fetchSubmission } from '$lib/utils/services/submissions';
|
||||
import {
|
||||
fetchSubmission,
|
||||
updateQuestionAnswer,
|
||||
updateSubmission
|
||||
} from '$lib/utils/services/submissions';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import {
|
||||
@@ -26,8 +30,9 @@
|
||||
} from '$lib/utils/services/notification/notification';
|
||||
import { lesson } from '../store/lessons';
|
||||
import { browser } from '$app/environment';
|
||||
import { CloseFilled } from 'carbon-icons-svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
import { useCompletion } from 'ai/svelte';
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
import GradingLoader from './GradingLoader.svelte';
|
||||
|
||||
export let preview: boolean = false;
|
||||
export let exerciseId = '';
|
||||
@@ -40,6 +45,9 @@
|
||||
let isLoadingAutoSavedData = false;
|
||||
let alreadyCheckedAutoSavedData = false;
|
||||
let gradedComment = true;
|
||||
let isLoading = false;
|
||||
let submissionResponse;
|
||||
let submissionId;
|
||||
|
||||
function handleStart() {
|
||||
$questionnaireMetaData.currentQuestionIndex += 1;
|
||||
@@ -80,7 +88,89 @@
|
||||
});
|
||||
};
|
||||
|
||||
function onSubmit(id, value, moveToNextQuestion = false) {
|
||||
const { input, handleSubmit, completion } = useCompletion({
|
||||
api: '/api/completion/gradingprompt',
|
||||
onFinish: async () => {
|
||||
try {
|
||||
const responseData = $completion.replace('```json', '').replace('```', '');
|
||||
|
||||
let aiResponses = [];
|
||||
try {
|
||||
// Parse the modified response data as JSON
|
||||
aiResponses = JSON.parse(responseData);
|
||||
} catch (error) {
|
||||
console.error('Error parsing AI response', error);
|
||||
}
|
||||
|
||||
$questionnaire.questions.forEach((question) => {
|
||||
const { id, points, question_type_id } = question;
|
||||
let questionAnswer = submissionResponse?.res?.data.find(
|
||||
(answer) => answer.question_id === id
|
||||
);
|
||||
|
||||
console.log('this is the question answer', questionAnswer);
|
||||
if (question_type_id !== QUESTION_TYPE.TEXTAREA) {
|
||||
const answer = $questionnaireMetaData.answers[question.name];
|
||||
let score = points / answer.length;
|
||||
$questionnaireMetaData.grades = {
|
||||
...$questionnaireMetaData.grades,
|
||||
[id]: score
|
||||
};
|
||||
updateQuestionAnswer({ point: score }, { id: questionAnswer?.id });
|
||||
} else if (aiResponses.length) {
|
||||
const graded = aiResponses.find((res) => res.id === id);
|
||||
|
||||
$questionnaireMetaData.grades = {
|
||||
...$questionnaireMetaData.grades,
|
||||
[id]: graded.score
|
||||
};
|
||||
updateQuestionAnswer({ point: graded.score }, { id: questionAnswer?.id });
|
||||
}
|
||||
});
|
||||
|
||||
$questionnaireMetaData.finalTotalGrade = Object.values(
|
||||
$questionnaireMetaData.grades
|
||||
).reduce((acc, answer): number => {
|
||||
return acc + answer;
|
||||
}, 0);
|
||||
|
||||
updateSubmission({
|
||||
id: submissionId,
|
||||
status_id: STATUS.GRADED,
|
||||
total: $questionnaireMetaData.finalTotalGrade
|
||||
});
|
||||
|
||||
$questionnaireMetaData.status = STATUS.GRADED;
|
||||
console.log('grade', $questionnaireMetaData.finalTotalGrade);
|
||||
} catch (error) {
|
||||
console.error('Error', error);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function automaticGrading(answers, questions) {
|
||||
const paragraphAiInput = questions
|
||||
.filter((q) => q.question_type_id === QUESTION_TYPE.TEXTAREA)
|
||||
.map((q) => {
|
||||
const answer = answers?.answers[q.name]; // { open_answer: '' }
|
||||
return {
|
||||
id: q.id,
|
||||
question: q.title,
|
||||
answer: answer,
|
||||
point: q.points
|
||||
};
|
||||
});
|
||||
$input = JSON.stringify(paragraphAiInput);
|
||||
handleSubmit({ preventDefault: () => {} });
|
||||
}
|
||||
|
||||
async function onSubmit(id, value, moveToNextQuestion = false) {
|
||||
if ($course.type === COURSE_TYPE.SELF_PACED) {
|
||||
isLoading = true;
|
||||
}
|
||||
|
||||
const { answers } = $questionnaireMetaData;
|
||||
const { questions } = $questionnaire;
|
||||
const prevAnswer = answers[id] || [];
|
||||
@@ -114,14 +204,24 @@
|
||||
$questionnaireMetaData.status = 1;
|
||||
$questionnaireMetaData.totalPossibleGrade = getTotalPossibleGrade($questionnaire.questions);
|
||||
$questionnaireMetaData.grades = {};
|
||||
($questionnaireMetaData.comment = ''),
|
||||
submitExercise(
|
||||
$questionnaireMetaData.answers,
|
||||
questions,
|
||||
exerciseId,
|
||||
$course.id,
|
||||
getGroupMemberId($group.people, $profile.id)
|
||||
);
|
||||
|
||||
$questionnaireMetaData.comment = '';
|
||||
let response = await submitExercise(
|
||||
$questionnaireMetaData.answers,
|
||||
questions,
|
||||
exerciseId,
|
||||
$course.id,
|
||||
getGroupMemberId($group.people, $profile.id)
|
||||
);
|
||||
if (response) {
|
||||
submissionResponse = response;
|
||||
submissionId = submissionResponse.submission[0]?.id;
|
||||
}
|
||||
|
||||
if ($course.type === COURSE_TYPE.SELF_PACED) {
|
||||
automaticGrading($questionnaireMetaData, $questionnaire.questions);
|
||||
}
|
||||
|
||||
notifyEducator();
|
||||
}
|
||||
}
|
||||
@@ -291,7 +391,9 @@
|
||||
</div>
|
||||
</RoleBasedSecurity>
|
||||
{:else if $questionnaireMetaData.isFinished}
|
||||
{#if !isLoadingAutoSavedData}
|
||||
{#if isLoading}
|
||||
<GradingLoader />
|
||||
{:else if !isLoading && !isLoadingAutoSavedData}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col lg:flex-row items-start lg:items-center lg:space-x-4 w-full">
|
||||
<h2 class="text-xl font-normal">{$questionnaire.title}</h2>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<script lang="ts">
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import Select from '$lib/components/Form/Select.svelte';
|
||||
import {
|
||||
lessons,
|
||||
handleSaveLesson
|
||||
} from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import { course, group } from '$lib/components/Course/store';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { handleAddLessonWidget } from '../Navigation/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
import type { Lesson } from '$lib/utils/types';
|
||||
|
||||
let errors = {
|
||||
title: ''
|
||||
};
|
||||
let lesson: Lesson = {
|
||||
id: '',
|
||||
course_id: $course.id || '',
|
||||
title: '',
|
||||
profile: undefined,
|
||||
call_url: undefined,
|
||||
lesson_at: new Date().toDateString(),
|
||||
is_unlocked: true,
|
||||
lesson_completion: []
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!lesson.title.trim()) {
|
||||
errors.title = 'title cannot be empty';
|
||||
return;
|
||||
}
|
||||
const savedLesson = await handleSaveLesson(lesson, $course.id);
|
||||
|
||||
if (Array.isArray(savedLesson) && savedLesson[0]) {
|
||||
const newLesson = savedLesson[0];
|
||||
lesson.id = newLesson.id;
|
||||
$lessons = [...$lessons, lesson];
|
||||
goto('/courses/' + $course.id + '/lessons/' + lesson.id);
|
||||
}
|
||||
|
||||
$handleAddLessonWidget.open = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
onClose={() => ($handleAddLessonWidget.open = false)}
|
||||
bind:open={$handleAddLessonWidget.open}
|
||||
width="w-[80%] md:w-[65%]"
|
||||
maxWidth="max-w-2xl"
|
||||
containerClass="overflow-hidden"
|
||||
modalHeading={$t('course.navItem.lessons.add_lesson.modal_heading')}
|
||||
>
|
||||
<div
|
||||
class="relative m-auto py-2 md:py-3 px-2 md:px-5 mb-2 md:mb-4 flex flex-wrap items-center dark:bg-neutral-800"
|
||||
>
|
||||
<div class="w-full">
|
||||
<TextField
|
||||
label={$t('course.navItem.lessons.add_lesson.lesson_title')}
|
||||
bind:value={lesson.title}
|
||||
autoFocus={true}
|
||||
className="flex-1 min-w-lg max-w-lg"
|
||||
isRequired={true}
|
||||
errorMessage={errors.title}
|
||||
/>
|
||||
{#if $course.type == COURSE_TYPE.LIVE_CLASS}
|
||||
<div
|
||||
class="flex items-start justify-evenly gap-1 flex-col lg:flex-row lg:items-center mt-2 w-4/5"
|
||||
>
|
||||
<div class="lg:mb-0">
|
||||
<Select
|
||||
bind:value={lesson.profile}
|
||||
options={$group.tutors}
|
||||
labelKey="fullname"
|
||||
className="sm:my-1 w-[100%]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center lg:mb-0">
|
||||
<input
|
||||
type="date"
|
||||
name="lesson-date-picker"
|
||||
class="p-2 my-2 rounded-md sm:w-[179px] dark:bg-neutral-800 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-3 lg:mb-0">
|
||||
<TextField className="w-[179px]" placeholder="https://meet.google.com/mga-dsjs-fmb" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row-reverse">
|
||||
<PrimaryButton label={$t('course.navItem.lessons.add_lesson.save')} onClick={handleSave} />
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -39,6 +39,17 @@ export function validateQuestionnaire(questions) {
|
||||
}
|
||||
const qErrors = errors[question.id] || {};
|
||||
|
||||
if (question.question_type.id !== QUESTION_TYPE.TEXTAREA) {
|
||||
const hasEmptyOptionLabel = question.options
|
||||
.filter((option) => !option.deleted_at)
|
||||
.some((option) => option.label.trim() === '');
|
||||
|
||||
if (hasEmptyOptionLabel) {
|
||||
qErrors.option = 'Please enter a label for all options';
|
||||
errors[question.id] = { ...qErrors };
|
||||
}
|
||||
}
|
||||
|
||||
const hasAnswer = question.options
|
||||
.filter((o) => !o.deleted_at)
|
||||
.some((option) => option.is_correct);
|
||||
|
||||
@@ -87,7 +87,7 @@ export async function handleSaveLesson(lesson: Lesson, course_id: Course['id'])
|
||||
|
||||
let newLessonData: any[] | null = null;
|
||||
|
||||
if (typeof lesson.id === 'string') {
|
||||
if (!!lesson.id) {
|
||||
// No need to get the result of update cause we have all in local state
|
||||
await updateLesson(newLesson, lesson.id);
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { getIsLessonComplete } from '../Lesson/functions';
|
||||
import { currentOrg, isFreePlan } from '$lib/utils/store/org';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
|
||||
export let path: string;
|
||||
export let isStudent: boolean = false;
|
||||
@@ -158,7 +159,12 @@
|
||||
label: $t('course.navItems.nav_attendance'),
|
||||
to: getNavItemRoute($course.id, 'attendance'),
|
||||
isPaidFeature: false,
|
||||
hideSortIcon: true
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
if ($course.type !== COURSE_TYPE.LIVE_CLASS) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('course.navItems.nav_submissions'),
|
||||
@@ -166,7 +172,11 @@
|
||||
hideSortIcon: true,
|
||||
isPaidFeature: false,
|
||||
show() {
|
||||
return !isStudent;
|
||||
if (isStudent) return false;
|
||||
|
||||
if ($course.type !== COURSE_TYPE.LIVE_CLASS) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -175,16 +185,11 @@
|
||||
isPaidFeature: false,
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
return isStudent ? $currentOrg.customization.course.grading : true;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('course.navItems.nav_people'),
|
||||
to: getNavItemRoute($course.id, 'people'),
|
||||
isPaidFeature: false,
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
return !isStudent;
|
||||
if ($course.type == COURSE_TYPE.LIVE_CLASS) {
|
||||
return isStudent ? $currentOrg.customization.course.grading : true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -209,6 +214,15 @@
|
||||
return !isStudent;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('course.navItems.nav_people'),
|
||||
to: getNavItemRoute($course.id, 'people'),
|
||||
isPaidFeature: false,
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
return !isStudent;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('course.navItems.nav_settings'),
|
||||
to: getNavItemRoute($course.id, 'settings'),
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { CodeSnippet, Grid, Row, Column, Toggle } from 'carbon-components-svelte';
|
||||
import {
|
||||
CodeSnippet,
|
||||
Grid,
|
||||
Row,
|
||||
Column,
|
||||
Toggle,
|
||||
RadioButtonGroup,
|
||||
RadioButton
|
||||
} from 'carbon-components-svelte';
|
||||
import { Restart } from 'carbon-icons-svelte';
|
||||
import { PUBLIC_SERVER_URL } from '$env/static/public';
|
||||
|
||||
@@ -26,6 +34,7 @@
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
import generateSlug from '$lib/utils/functions/generateSlug';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
|
||||
let isSaving = false;
|
||||
let isLoading = false;
|
||||
@@ -124,6 +133,7 @@
|
||||
const {
|
||||
course_title,
|
||||
course_description,
|
||||
type,
|
||||
logo,
|
||||
tabs,
|
||||
grading,
|
||||
@@ -134,6 +144,7 @@
|
||||
await updateCourse($course.id, avatar, {
|
||||
title: course_title,
|
||||
description: course_description,
|
||||
type: type,
|
||||
logo: logo,
|
||||
is_published,
|
||||
metadata: {
|
||||
@@ -148,6 +159,7 @@
|
||||
|
||||
$course.title = course_title;
|
||||
$course.description = course_description;
|
||||
$course.type = type;
|
||||
$course.logo = logo;
|
||||
$course.is_published = is_published;
|
||||
$course.metadata = {
|
||||
@@ -169,6 +181,7 @@
|
||||
if (course && Object.keys(course).length && $settings.course_title !== course.title) {
|
||||
$settings = {
|
||||
course_title: course.title,
|
||||
type: course.type,
|
||||
course_description: course.description,
|
||||
logo: course.logo || '',
|
||||
tabs: course.metadata.lessonTabsOrder || $settings.tabs,
|
||||
@@ -317,6 +330,25 @@
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>{$t('course.navItem.settings.type')}</SectionTitle>
|
||||
<p>{$t('course.navItem.settings.course_type_desc')}</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<RadioButtonGroup hideLegend bind:selected={$settings.type}>
|
||||
<RadioButton
|
||||
labelText={$t('course.navItem.settings.live_class')}
|
||||
value={COURSE_TYPE.LIVE_CLASS}
|
||||
/>
|
||||
<RadioButton
|
||||
labelText={$t('course.navItem.settings.self_paced')}
|
||||
value={COURSE_TYPE.SELF_PACED}
|
||||
/>
|
||||
</RadioButtonGroup>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>{$t('course.navItem.settings.allow')}</SectionTitle>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const settings = writable({
|
||||
@@ -5,6 +6,7 @@ export const settings = writable({
|
||||
course_title: '',
|
||||
course_description: '',
|
||||
grading: false,
|
||||
type: COURSE_TYPE.LIVE_CLASS,
|
||||
allow_new_students: false,
|
||||
tabs: [
|
||||
{ id: 1, name: 'course.navItem.lessons.materials.tabs.note.title' },
|
||||
|
||||
@@ -3,11 +3,13 @@ import type { Writable } from 'svelte/store';
|
||||
import { lessons } from './components/Lesson/store/lessons';
|
||||
import { ROLE } from '$lib/utils/constants/roles';
|
||||
import type { Course, GroupPerson } from '$lib/utils/types';
|
||||
import { COURSE_TYPE } from '$lib/utils/types';
|
||||
|
||||
export const defaultCourse: Course = {
|
||||
id: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: COURSE_TYPE.LIVE_CLASS,
|
||||
cost: 0,
|
||||
currency: '',
|
||||
status: 'ACTIVE',
|
||||
|
||||
Reference in New Issue
Block a user