feat: instructors in courses and batches

This commit is contained in:
Jannat Patel
2024-06-25 18:43:28 +05:30
parent 8625ac048a
commit 96028c9f42
12 changed files with 449 additions and 75 deletions

View File

@@ -80,8 +80,22 @@
<div class="text-2xl font-semibold mb-2">
{{ batch.data.title }}
</div>
<div v-html="batch.data.description" class="leading-5 mb-4"></div>
<div v-html="batch.data.description" class="leading-5 mb-2"></div>
<div class="flex avatar-group overlap mb-5">
<div
class="mr-1"
:class="{
'avatar-group overlap': batch.data.instructors.length > 1,
}"
>
<UserAvatar
v-for="instructor in batch.data.instructors"
:user="instructor"
/>
</div>
<CourseInstructors :instructors="batch.data.instructors" />
</div>
<DateRange
:startDate="batch.data.start_date"
:endDate="batch.data.end_date"
@@ -155,6 +169,8 @@
<script setup>
import { Breadcrumbs, Button, createResource, Tabs, Badge } from 'frappe-ui'
import { computed, inject, ref } from 'vue'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import {
Clock,
LayoutDashboard,

View File

@@ -8,8 +8,8 @@
{{ __('Save') }}
</Button>
</header>
<div class="py-5">
<div class="container border-b">
<div class="w-1/2 mx-auto py-5">
<div class="">
<div class="text-lg font-semibold mb-4">
{{ __('Details') }}
</div>
@@ -20,54 +20,13 @@
:label="__('Title')"
class="mb-4"
/>
</div>
<div class="flex flex-col space-y-2">
<FormControl
v-model="batch.published"
type="checkbox"
:label="__('Published')"
/>
</div>
<div class="flex flex-col">
<FileUploader
v-if="!batch.image"
class="mt-4"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template
v-slot="{ file, progress, uploading, openFileSelector }"
>
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{
uploading ? `Uploading ${progress}%` : 'Upload an image'
}}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="my-4">
<div class="text-xs text-gray-600 mb-1">
{{ __('Meta Image') }}
</div>
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5 text-gray-700" />
</div>
<div class="flex flex-col">
<span>
{{ batch.image.file_name }}
</span>
<span class="text-sm text-gray-500 mt-1">
{{ getFileSize(batch.image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-gray-200 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
</div>
<FormControl
v-model="batch.allow_self_enrollment"
type="checkbox"
@@ -76,7 +35,53 @@
</div>
</div>
</div>
<div class="container border-b mb-4">
<div class="mb-4">
<div>
<FileUploader
v-if="!batch.image"
class="mt-4"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{ uploading ? `Uploading ${progress}%` : 'Upload an image' }}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="text-xs text-gray-600 mb-1">
{{ __('Meta Image') }}
</div>
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5 text-gray-700" />
</div>
<div class="flex flex-col">
<span>
{{ batch.image.file_name }}
</span>
<span class="text-sm text-gray-500 mt-1">
{{ getFileSize(batch.image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-gray-200 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
</div>
</div>
<MultiSelect
v-model="instructors"
doctype="User"
:label="__('Instructors')"
/>
</div>
<div class="mb-4">
<FormControl
v-model="batch.description"
:label="__('Description')"
@@ -96,7 +101,7 @@
/>
</div>
</div>
<div class="container border-b mb-4">
<div class="mb-4">
<div class="text-lg font-semibold mb-4">
{{ __('Date and Time') }}
</div>
@@ -137,7 +142,7 @@
</div>
</div>
</div>
<div class="container border-b mb-4">
<div class="mb-4">
<div class="text-lg font-semibold mb-4">
{{ __('Settings') }}
</div>
@@ -182,7 +187,7 @@
</div>
</div>
<div class="container">
<div class="">
<div class="text-lg font-semibold mb-4">
{{ __('Payment') }}
</div>
@@ -210,7 +215,14 @@
</div>
</template>
<script setup>
import { computed, onMounted, inject, reactive, onBeforeUnmount } from 'vue'
import {
computed,
onMounted,
inject,
reactive,
onBeforeUnmount,
ref,
} from 'vue'
import {
Breadcrumbs,
FormControl,
@@ -220,6 +232,7 @@ import {
createResource,
} from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import { useRouter } from 'vue-router'
import { getFileSize, showToast } from '../utils'
import { X, FileText } from 'lucide-vue-next'
@@ -255,6 +268,8 @@ const batch = reactive({
amount: 0,
})
const instructors = ref([])
onMounted(() => {
if (!user.data) window.location.href = '/login'
if (props.batchName != 'new') {
@@ -285,6 +300,9 @@ const newBatch = createResource({
doc: {
doctype: 'LMS Batch',
meta_image: batch.image?.file_url,
instructors: instructors.value.map((instructor) => ({
instructor: instructor,
})),
...batch,
},
}
@@ -301,9 +319,13 @@ const batchDetail = createResource({
},
onSuccess(data) {
Object.keys(data).forEach((key) => {
if (Object.hasOwn(batch, key)) batch[key] = data[key]
if (key == 'instructors') {
data.instructors.forEach((instructor) => {
instructors.value.push(instructor.instructor)
})
} else if (Object.hasOwn(batch, key)) batch[key] = data[key]
})
let checkboxes = ['published', 'paid_batch']
let checkboxes = ['published', 'paid_batch', 'allow_self_enrollment']
for (let idx in checkboxes) {
let key = checkboxes[idx]
batch[key] = batch[key] ? true : false
@@ -320,6 +342,9 @@ const editBatch = createResource({
name: props.batchName,
fieldname: {
meta_image: batch.image?.file_url,
instructors: instructors.value.map((instructor) => ({
instructor: instructor,
})),
...batch,
},
}

View File

@@ -36,6 +36,20 @@
</span>
</div>
</div>
<div class="flex avatar-group overlap mt-3">
<div
class="mr-1"
:class="{
'avatar-group overlap': batch.data.instructors.length > 1,
}"
>
<UserAvatar
v-for="instructor in batch.data.instructors"
:user="instructor"
/>
</div>
<CourseInstructors :instructors="batch.data.instructors" />
</div>
</div>
<div class="grid lg:grid-cols-[60%,20%] gap-4 lg:gap-20 mt-10">
<div class="order-2 lg:order-none">
@@ -91,6 +105,8 @@ import { Breadcrumbs, createResource } from 'frappe-ui'
import CourseCard from '@/components/CourseCard.vue'
import BatchOverlay from '@/components/BatchOverlay.vue'
import DateRange from '../components/Common/DateRange.vue'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
const user = inject('$user')
const router = useRouter()

View File

@@ -62,7 +62,7 @@
<template #default="{ tab }">
<div
v-if="tab.batches && tab.batches.value.length"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 mt-5 mx-5"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 m-5"
>
<router-link
v-for="batch in tab.batches.value"

View File

@@ -99,12 +99,13 @@
:label="__('Preview Video')"
class="mb-4"
/>
<div>
<div class="mb-4">
<div class="mb-1.5 text-xs text-gray-600">
{{ __('Tags') }}
</div>
<div class="flex items-center">
<div
v-if="course.tags"
v-for="tag in course.tags?.split(', ')"
class="flex items-center bg-gray-100 p-2 rounded-md mr-2"
>
@@ -121,6 +122,11 @@
/>
</div>
</div>
<MultiSelect
v-model="instructors"
doctype="User"
:label="__('Instructors')"
/>
</div>
<div class="container border-t">
<div class="text-lg font-semibold mt-5 mb-4">
@@ -188,7 +194,6 @@
</div>
</div>
<div class="border-l pt-5">
<!-- <CreateOutline v-if="courseResource.doc" :course="courseResource.doc"/> -->
<CourseOutline
v-if="courseResource.data"
:courseName="courseResource.data.name"
@@ -222,6 +227,7 @@ import Link from '@/components/Controls/Link.vue'
import { FileText, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import CourseOutline from '@/components/CourseOutline.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
const user = inject('$user')
const newTag = ref('')
@@ -288,6 +294,9 @@ const courseCreationResource = createResource({
doc: {
doctype: 'LMS Course',
image: course.course_image?.file_url || '',
instructors: instructors.value.map((instructor) => ({
instructor: instructor,
})),
...values,
},
}
@@ -303,6 +312,9 @@ const courseEditResource = createResource({
name: values.course,
fieldname: {
image: course.course_image?.file_url || '',
instructors: instructors.value.map((instructor) => ({
instructor: instructor,
})),
...course,
},
}
@@ -320,7 +332,12 @@ const courseResource = createResource({
auto: false,
onSuccess(data) {
Object.keys(data).forEach((key) => {
if (Object.hasOwn(course, key)) course[key] = data[key]
if (key == 'instructors') {
instructors.value = []
data.instructors.forEach((instructor) => {
instructors.value.push(instructor.instructor)
})
} else if (Object.hasOwn(course, key)) course[key] = data[key]
})
let checkboxes = [
'published',
@@ -334,7 +351,6 @@ const courseResource = createResource({
}
if (data.image) imageResource.reload({ image: data.image })
instructors.value = data.instructors
check_permission()
},
})
@@ -446,7 +462,7 @@ const check_permission = () => {
if (user.data?.is_moderator) return
instructors.value.forEach((instructor) => {
if (!user_is_instructor && instructor.instructor == user.data?.name) {
if (!user_is_instructor && instructor == user.data?.name) {
user_is_instructor = true
}
})