chore: resolved conflicts
This commit is contained in:
@@ -70,7 +70,7 @@ const props = withDefaults(
|
|||||||
modelValue: string
|
modelValue: string
|
||||||
label?: string
|
label?: string
|
||||||
description?: string
|
description?: string
|
||||||
type: 'image' | 'video'
|
type?: 'image' | 'video'
|
||||||
required?: boolean
|
required?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -285,9 +285,6 @@ const certificate = createResource({
|
|||||||
}&format=${encodeURIComponent(data.template)}`,
|
}&format=${encodeURIComponent(data.template)}`,
|
||||||
'_blank'
|
'_blank'
|
||||||
)
|
)
|
||||||
capture('certificate_issued', {
|
|
||||||
course: props.course.data.name,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -52,14 +52,12 @@ import {
|
|||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import SettingFields from '@/components/Settings/SettingFields.vue'
|
import SettingFields from '@/components/Settings/SettingFields.vue'
|
||||||
import { useTelemetry } from 'frappe-ui/frappe'
|
|
||||||
|
|
||||||
const show = defineModel<boolean>({ required: true, default: false })
|
const show = defineModel<boolean>({ required: true, default: false })
|
||||||
const paymentGateways = defineModel<any>('paymentGateways')
|
const paymentGateways = defineModel<any>('paymentGateways')
|
||||||
const newGateway = ref(null)
|
const newGateway = ref(null)
|
||||||
const newGatewayFields = ref([])
|
const newGatewayFields = ref([])
|
||||||
const newGatewayData = ref<Record<string, any>>({})
|
const newGatewayData = ref<Record<string, any>>({})
|
||||||
const { capture } = useTelemetry()
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
gatewayID: string | null
|
gatewayID: string | null
|
||||||
@@ -160,7 +158,6 @@ const saveNewGateway = (close: () => void) => {
|
|||||||
...newGatewayData.value,
|
...newGatewayData.value,
|
||||||
},
|
},
|
||||||
}).then((data: any) => {
|
}).then((data: any) => {
|
||||||
capture('payment_gateway_configured')
|
|
||||||
paymentGateways.value.reload()
|
paymentGateways.value.reload()
|
||||||
close()
|
close()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,15 +4,30 @@
|
|||||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||||
{{ __('Upcoming Evaluations') }}
|
{{ __('Upcoming Evaluations') }}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button v-if="canScheduleEvals" @click="openEvalModal">
|
||||||
v-if="
|
|
||||||
upcoming_evals.data?.length != evaluationCourses.length && !forHome
|
|
||||||
"
|
|
||||||
@click="openEvalModal"
|
|
||||||
>
|
|
||||||
{{ __('Schedule Evaluation') }}
|
{{ __('Schedule Evaluation') }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="endDate && !endDateHasPassed"
|
||||||
|
class="text-sm leading-5 bg-surface-amber-1 text-ink-amber-3 p-2 rounded-md mb-4"
|
||||||
|
>
|
||||||
|
{{ __('The last day to schedule your evaluations is ') }}
|
||||||
|
<span class="font-medium">
|
||||||
|
{{ dayjs(endDate).format('DD MMMM YYYY') }} </span
|
||||||
|
>.
|
||||||
|
{{ __('Please make sure to schedule your evaluation before this date.') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="endDateHasPassed"
|
||||||
|
class="text-sm leading-5 bg-surface-red-1 text-ink-red-3 p-2 rounded-md mb-4"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'The deadline to schedule evaluations has passed. Please contact the Instructor for assistance.'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
<div v-if="upcoming_evals.data?.length">
|
<div v-if="upcoming_evals.data?.length">
|
||||||
<div
|
<div
|
||||||
class="grid gap-4"
|
class="grid gap-4"
|
||||||
@@ -99,7 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-ink-gray-5">
|
<div v-else-if="!endDateHasPassed" class="text-ink-gray-5">
|
||||||
{{ __('Schedule an evaluation to get certified.') }}
|
{{ __('Schedule an evaluation to get certified.') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,6 +188,18 @@ const evaluationCourses = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const canScheduleEvals = computed(() => {
|
||||||
|
return (
|
||||||
|
upcoming_evals.data?.length != evaluationCourses.length &&
|
||||||
|
!props.forHome &&
|
||||||
|
!endDateHasPassed.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const endDateHasPassed = computed(() => {
|
||||||
|
return dayjs().isSameOrAfter(dayjs(props.endDate))
|
||||||
|
})
|
||||||
|
|
||||||
const cancelEvaluation = (evl) => {
|
const cancelEvaluation = (evl) => {
|
||||||
$dialog({
|
$dialog({
|
||||||
title: __('Cancel this evaluation?'),
|
title: __('Cancel this evaluation?'),
|
||||||
|
|||||||
@@ -144,20 +144,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="batch.data.evaluation_end_date && isStudent"
|
|
||||||
class="text-sm leading-5 bg-surface-amber-1 text-ink-amber-3 p-2 rounded-md mb-10"
|
|
||||||
>
|
|
||||||
{{ __('The last day to schedule your evaluations is ') }}
|
|
||||||
<span class="font-medium">
|
|
||||||
{{
|
|
||||||
dayjs(batch.data.evaluation_end_date).format('DD MMMM YYYY')
|
|
||||||
}} </span
|
|
||||||
>.
|
|
||||||
{{
|
|
||||||
__('Please make sure to schedule your evaluation before this date.')
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div v-if="dayjs().isSameOrAfter(dayjs(batch.data.start_date))">
|
<div v-if="dayjs().isSameOrAfter(dayjs(batch.data.start_date))">
|
||||||
<div class="text-ink-gray-7 font-semibold mb-2">
|
<div class="text-ink-gray-7 font-semibold mb-2">
|
||||||
{{ __('Feedback') }}
|
{{ __('Feedback') }}
|
||||||
|
|||||||
@@ -281,21 +281,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
FormControl,
|
FormControl,
|
||||||
FileUploader,
|
|
||||||
Button,
|
Button,
|
||||||
TextEditor,
|
TextEditor,
|
||||||
createResource,
|
createResource,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
toast,
|
toast,
|
||||||
call,
|
call,
|
||||||
Toast,
|
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { Image, Trash2 } from 'lucide-vue-next'
|
|
||||||
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
|
||||||
import { sessionStore } from '../stores/session'
|
|
||||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
|
||||||
import Link from '@/components/Controls/Link.vue'
|
|
||||||
import {
|
import {
|
||||||
escapeHTML,
|
escapeHTML,
|
||||||
getMetaInfo,
|
getMetaInfo,
|
||||||
@@ -303,7 +295,13 @@ import {
|
|||||||
sanitizeHTML,
|
sanitizeHTML,
|
||||||
updateMetaInfo,
|
updateMetaInfo,
|
||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { Trash2 } from 'lucide-vue-next'
|
||||||
|
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
||||||
|
import { sessionStore } from '../stores/session'
|
||||||
import Uploader from '@/components/Controls/Uploader.vue'
|
import Uploader from '@/components/Controls/Uploader.vue'
|
||||||
|
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
@@ -466,6 +464,7 @@ const validateFields = () => {
|
|||||||
!['description', 'batch_details'].includes(key) &&
|
!['description', 'batch_details'].includes(key) &&
|
||||||
typeof batch[key] === 'string'
|
typeof batch[key] === 'string'
|
||||||
) {
|
) {
|
||||||
|
console.log(key)
|
||||||
batch[key] = escapeHTML(batch[key])
|
batch[key] = escapeHTML(batch[key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,58 +75,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||||
<div class="mb-4">
|
<Uploader
|
||||||
<div class="text-xs text-ink-gray-5 mb-2">
|
v-model="course.image"
|
||||||
{{ __('Course Image') }}
|
:label="__('Course Image')"
|
||||||
</div>
|
:required="false"
|
||||||
<FileUploader
|
/>
|
||||||
v-if="!course.course_image"
|
|
||||||
:fileTypes="['image/*']"
|
|
||||||
:validateFile="validateFile"
|
|
||||||
@success="(file) => saveImage(file)"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-slot="{ file, progress, uploading, openFileSelector }"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div
|
|
||||||
class="border rounded-md w-fit py-5 px-20 cursor-pointer"
|
|
||||||
@click="openFileSelector"
|
|
||||||
>
|
|
||||||
<Image class="size-5 stroke-1 text-ink-gray-7" />
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<Button @click="openFileSelector">
|
|
||||||
{{ __('Upload') }}
|
|
||||||
</Button>
|
|
||||||
<div class="mt-1 text-ink-gray-5 text-sm leading-5">
|
|
||||||
{{
|
|
||||||
__('Appears on the course card in the course list')
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FileUploader>
|
|
||||||
<div v-else class="mb-4">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img
|
|
||||||
:src="course.course_image.file_url"
|
|
||||||
class="border rounded-md w-40"
|
|
||||||
/>
|
|
||||||
<div class="ml-4">
|
|
||||||
<Button @click="removeImage()">
|
|
||||||
{{ __('Remove') }}
|
|
||||||
</Button>
|
|
||||||
<div class="mt-2 text-ink-gray-5 text-sm">
|
|
||||||
{{
|
|
||||||
__('Appears on the course card in the course list')
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ColorSwatches
|
<ColorSwatches
|
||||||
v-model="course.card_gradient"
|
v-model="course.card_gradient"
|
||||||
@@ -333,7 +286,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
createResource,
|
createResource,
|
||||||
FormControl,
|
FormControl,
|
||||||
FileUploader,
|
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
@@ -347,22 +299,22 @@ import {
|
|||||||
watch,
|
watch,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { Image, Trash2, X } from 'lucide-vue-next'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
|
||||||
import { sessionStore } from '../stores/session'
|
|
||||||
import {
|
import {
|
||||||
escapeHTML,
|
escapeHTML,
|
||||||
getMetaInfo,
|
getMetaInfo,
|
||||||
openSettings,
|
openSettings,
|
||||||
sanitizeHTML,
|
sanitizeHTML,
|
||||||
updateMetaInfo,
|
updateMetaInfo,
|
||||||
validateFile,
|
|
||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
|
import { Trash2, X } from 'lucide-vue-next'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
||||||
|
import { sessionStore } from '../stores/session'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import CourseOutline from '@/components/CourseOutline.vue'
|
import CourseOutline from '@/components/CourseOutline.vue'
|
||||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||||
import ColorSwatches from '@/components/Controls/ColorSwatches.vue'
|
import ColorSwatches from '@/components/Controls/ColorSwatches.vue'
|
||||||
|
import Uploader from '@/components/Controls/Uploader.vue'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const newTag = ref('')
|
const newTag = ref('')
|
||||||
@@ -386,7 +338,7 @@ const course = reactive({
|
|||||||
short_introduction: '',
|
short_introduction: '',
|
||||||
description: '',
|
description: '',
|
||||||
video_link: '',
|
video_link: '',
|
||||||
course_image: null,
|
image: null,
|
||||||
card_gradient: '',
|
card_gradient: '',
|
||||||
tags: '',
|
tags: '',
|
||||||
category: '',
|
category: '',
|
||||||
@@ -448,7 +400,7 @@ const courseCreationResource = createResource({
|
|||||||
return {
|
return {
|
||||||
doc: {
|
doc: {
|
||||||
doctype: 'LMS Course',
|
doctype: 'LMS Course',
|
||||||
image: course.course_image?.file_url || '',
|
image: course.image,
|
||||||
instructors: instructors.value.map((instructor) => ({
|
instructors: instructors.value.map((instructor) => ({
|
||||||
instructor: instructor,
|
instructor: instructor,
|
||||||
})),
|
})),
|
||||||
@@ -469,7 +421,7 @@ const courseEditResource = createResource({
|
|||||||
doctype: 'LMS Course',
|
doctype: 'LMS Course',
|
||||||
name: values.course,
|
name: values.course,
|
||||||
fieldname: {
|
fieldname: {
|
||||||
image: course.course_image?.file_url || '',
|
image: course.image,
|
||||||
instructors: instructors.value.map((instructor) => ({
|
instructors: instructors.value.map((instructor) => ({
|
||||||
instructor: instructor,
|
instructor: instructor,
|
||||||
})),
|
})),
|
||||||
@@ -519,24 +471,10 @@ const courseResource = createResource({
|
|||||||
course[key] = course[key] ? true : false
|
course[key] = course[key] ? true : false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.image) imageResource.reload({ image: data.image })
|
|
||||||
check_permission()
|
check_permission()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const imageResource = createResource({
|
|
||||||
url: 'lms.lms.api.get_file_info',
|
|
||||||
makeParams(values) {
|
|
||||||
return {
|
|
||||||
file_url: values.image,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auto: false,
|
|
||||||
onSuccess(data) {
|
|
||||||
course.course_image = data
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const validateFields = () => {
|
const validateFields = () => {
|
||||||
course.description = sanitizeHTML(course.description)
|
course.description = sanitizeHTML(course.description)
|
||||||
|
|
||||||
@@ -580,16 +518,12 @@ const createCourse = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editCourse = () => {
|
const editCourse = () => {
|
||||||
let was_published = courseResource.data.published
|
|
||||||
courseEditResource.submit(
|
courseEditResource.submit(
|
||||||
{
|
{
|
||||||
course: courseResource.data.name,
|
course: courseResource.data.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
if (!was_published && course.published) {
|
|
||||||
capture('publish_course')
|
|
||||||
}
|
|
||||||
updateMetaInfo('courses', props.courseName, meta)
|
updateMetaInfo('courses', props.courseName, meta)
|
||||||
toast.success(__('Course updated successfully'))
|
toast.success(__('Course updated successfully'))
|
||||||
},
|
},
|
||||||
@@ -657,14 +591,6 @@ const removeTag = (tag) => {
|
|||||||
newTag.value = ''
|
newTag.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveImage = (file) => {
|
|
||||||
course.course_image = file
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeImage = () => {
|
|
||||||
course.course_image = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const check_permission = () => {
|
const check_permission = () => {
|
||||||
let user_is_instructor = false
|
let user_is_instructor = false
|
||||||
if (user.data?.is_moderator) return
|
if (user.data?.is_moderator) return
|
||||||
|
|||||||
@@ -83,47 +83,11 @@
|
|||||||
class="mb-4"
|
class="mb-4"
|
||||||
:required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
<label class="block text-ink-gray-5 text-xs mb-1 mt-4">
|
<Uploader
|
||||||
{{ __('Company Logo') }}
|
v-model="job.company_logo"
|
||||||
<span class="text-ink-red-3">*</span>
|
:label="__('Company Logo')"
|
||||||
</label>
|
:required="false"
|
||||||
<FileUploader
|
/>
|
||||||
v-if="!job.image"
|
|
||||||
: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="">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="border rounded-md p-2 mr-2">
|
|
||||||
<FileText class="h-5 w-5 stroke-1.5 text-ink-gray-7" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span>
|
|
||||||
{{ job.image.file_name }}
|
|
||||||
</span>
|
|
||||||
<span class="text-sm text-ink-gray-4 mt-1">
|
|
||||||
{{ getFileSize(job.image.file_size) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<X
|
|
||||||
@click="removeImage()"
|
|
||||||
class="bg-surface-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,15 +114,14 @@ import {
|
|||||||
createResource,
|
createResource,
|
||||||
Button,
|
Button,
|
||||||
TextEditor,
|
TextEditor,
|
||||||
FileUploader,
|
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, onMounted, reactive, inject } from 'vue'
|
import { computed, onMounted, reactive, inject } from 'vue'
|
||||||
import { FileText, X } from 'lucide-vue-next'
|
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { escapeHTML, getFileSize, sanitizeHTML, validateFile } from '@/utils'
|
import { escapeHTML, sanitizeHTML } from '@/utils'
|
||||||
|
import Uploader from '@/components/Controls/Uploader.vue'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -177,7 +140,7 @@ const newJob = createResource({
|
|||||||
return {
|
return {
|
||||||
doc: {
|
doc: {
|
||||||
doctype: 'Job Opportunity',
|
doctype: 'Job Opportunity',
|
||||||
company_logo: job.image?.file_url,
|
company_logo: job.company_logo,
|
||||||
...job,
|
...job,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -191,7 +154,7 @@ const updateJob = createResource({
|
|||||||
doctype: 'Job Opportunity',
|
doctype: 'Job Opportunity',
|
||||||
name: props.jobName,
|
name: props.jobName,
|
||||||
fieldname: {
|
fieldname: {
|
||||||
company_logo: job.image.file_url,
|
company_logo: job.company_logo,
|
||||||
...job,
|
...job,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -215,20 +178,6 @@ const jobDetail = createResource({
|
|||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
if (Object.hasOwn(job, key)) job[key] = data[key]
|
if (Object.hasOwn(job, key)) job[key] = data[key]
|
||||||
})
|
})
|
||||||
if (data.company_logo) imageResource.reload({ image: data.company_logo })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const imageResource = createResource({
|
|
||||||
url: 'lms.lms.api.get_file_info',
|
|
||||||
makeParams(values) {
|
|
||||||
return {
|
|
||||||
file_url: values.image,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auto: false,
|
|
||||||
onSuccess(data) {
|
|
||||||
job.image = data
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -241,7 +190,7 @@ const job = reactive({
|
|||||||
status: 'Open',
|
status: 'Open',
|
||||||
company_name: '',
|
company_name: '',
|
||||||
company_website: '',
|
company_website: '',
|
||||||
image: null,
|
company_logo: null,
|
||||||
description: '',
|
description: '',
|
||||||
company_email_address: '',
|
company_email_address: '',
|
||||||
})
|
})
|
||||||
@@ -322,14 +271,6 @@ const validateJobFields = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveImage = (file) => {
|
|
||||||
job.image = file
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeImage = () => {
|
|
||||||
job.image = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobTypes = computed(() => {
|
const jobTypes = computed(() => {
|
||||||
return [
|
return [
|
||||||
{ label: 'Full Time', value: 'Full Time' },
|
{ label: 'Full Time', value: 'Full Time' },
|
||||||
|
|||||||
@@ -665,6 +665,15 @@ iframe {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codex-editor--narrow .ce-toolbox .ce-popover {
|
||||||
|
right: unset;
|
||||||
|
left: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ce-popover {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.cdx-search-field {
|
.cdx-search-field {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,7 +233,6 @@ import { ClipboardList, ListChecks, Plus, Trash2 } from 'lucide-vue-next'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { escapeHTML } from '@/utils'
|
import { escapeHTML } from '@/utils'
|
||||||
import Question from '@/components/Modals/Question.vue'
|
import Question from '@/components/Modals/Question.vue'
|
||||||
import { useTelemetry } from 'frappe-ui/frappe'
|
|
||||||
|
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const showQuestionModal = ref(false)
|
const showQuestionModal = ref(false)
|
||||||
@@ -242,7 +241,6 @@ const currentQuestion = reactive({
|
|||||||
marks: 0,
|
marks: 0,
|
||||||
name: '',
|
name: '',
|
||||||
})
|
})
|
||||||
const { capture } = useTelemetry()
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const readOnlyMode = window.read_only_mode
|
const readOnlyMode = window.read_only_mode
|
||||||
@@ -260,9 +258,7 @@ onMounted(() => {
|
|||||||
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
||||||
router.push({ name: 'Courses' })
|
router.push({ name: 'Courses' })
|
||||||
}
|
}
|
||||||
if (props.quizID !== 'new') {
|
quizDetails.reload()
|
||||||
quizDetails.reload()
|
|
||||||
}
|
|
||||||
window.addEventListener('keydown', keyboardShortcut)
|
window.addEventListener('keydown', keyboardShortcut)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -310,9 +306,6 @@ const submitQuiz = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
if (props.quizID === 'new') {
|
|
||||||
capture('quiz_created')
|
|
||||||
}
|
|
||||||
quizDetails.doc.total_marks = data.total_marks
|
quizDetails.doc.total_marks = data.total_marks
|
||||||
toast.success(__('Quiz updated successfully'))
|
toast.success(__('Quiz updated successfully'))
|
||||||
},
|
},
|
||||||
@@ -408,7 +401,7 @@ const breadcrumbs = computed(() => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
crumbs.push({
|
crumbs.push({
|
||||||
label: props.quizID == 'new' ? __('New Quiz') : quizDetails.doc?.title,
|
label: quizDetails.doc?.title,
|
||||||
route: { name: 'QuizForm', params: { quizID: props.quizID } },
|
route: { name: 'QuizForm', params: { quizID: props.quizID } },
|
||||||
})
|
})
|
||||||
return crumbs
|
return crumbs
|
||||||
@@ -416,7 +409,7 @@ const breadcrumbs = computed(() => {
|
|||||||
|
|
||||||
usePageMeta(() => {
|
usePageMeta(() => {
|
||||||
return {
|
return {
|
||||||
title: props.quizID == 'new' ? __('New Quiz') : quizDetails.doc?.title,
|
title: quizDetails.doc?.title,
|
||||||
icon: brand.favicon,
|
icon: brand.favicon,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1896,9 +1896,9 @@
|
|||||||
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
|
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "25.0.7"
|
version "25.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.7.tgz#afd2f0d34358631a287c6726dabe0c845abc2bae"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.8.tgz#e54e00f94fe1db2497b3e42d292b8376a2678c8d"
|
||||||
integrity sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==
|
integrity sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~7.16.0"
|
undici-types "~7.16.0"
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-12-04 12:53:38.246250",
|
"modified": "2026-01-14 08:53:16.672825",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch Enrollment",
|
"name": "LMS Batch Enrollment",
|
||||||
@@ -108,6 +108,18 @@
|
|||||||
"if_owner": 1,
|
"if_owner": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"role": "LMS Student"
|
"role": "LMS Student"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Batch Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-21 15:58:51.667270",
|
"modified": "2026-01-14 08:53:38.088168",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch Feedback",
|
"name": "LMS Batch Feedback",
|
||||||
@@ -105,6 +105,30 @@
|
|||||||
"role": "LMS Student",
|
"role": "LMS Student",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Batch Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -177,7 +177,7 @@
|
|||||||
"link_fieldname": "live_class"
|
"link_fieldname": "live_class"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-05-27 14:44:35.679712",
|
"modified": "2026-01-14 08:54:07.684781",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Live Class",
|
"name": "LMS Live Class",
|
||||||
@@ -215,6 +215,18 @@
|
|||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "LMS Student",
|
"role": "LMS Student",
|
||||||
"share": 1
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Batch Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -155,11 +155,17 @@ def create_attendance(live_class, data):
|
|||||||
doc = frappe.new_doc("LMS Live Class Participant")
|
doc = frappe.new_doc("LMS Live Class Participant")
|
||||||
doc.live_class = live_class.name
|
doc.live_class = live_class.name
|
||||||
doc.member = participant.get("user_email")
|
doc.member = participant.get("user_email")
|
||||||
doc.joined_at = participant.get("join_time")
|
doc.joined_at = get_datetime(participant.get("join_time"))
|
||||||
doc.left_at = participant.get("leave_time")
|
doc.left_at = get_datetime(participant.get("leave_time"))
|
||||||
doc.duration = participant.get("duration")
|
doc.duration = get_minutes(participant.get("duration"))
|
||||||
doc.insert()
|
doc.insert()
|
||||||
|
|
||||||
|
|
||||||
def update_attendees_count(live_class, data):
|
def update_attendees_count(live_class, data):
|
||||||
frappe.db.set_value("LMS Live Class", live_class.name, "attendees", len(data))
|
frappe.db.set_value("LMS Live Class", live_class.name, "attendees", len(data))
|
||||||
|
|
||||||
|
|
||||||
|
def get_minutes(duration_in_seconds):
|
||||||
|
if duration_in_seconds:
|
||||||
|
return int(duration_in_seconds) // 60
|
||||||
|
return 0
|
||||||
|
|||||||
@@ -89,8 +89,8 @@
|
|||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-27 22:32:24.196643",
|
"modified": "2026-01-14 08:54:21.421321",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Live Class Participant",
|
"name": "LMS Live Class Participant",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
@@ -106,6 +106,30 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Batch Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"row_format": "Dynamic",
|
"row_format": "Dynamic",
|
||||||
|
|||||||
@@ -1293,7 +1293,7 @@ def get_assignment_details(assessment, member):
|
|||||||
|
|
||||||
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
|
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
|
||||||
submission_name = existing_submission if existing_submission else "new-submission"
|
submission_name = existing_submission if existing_submission else "new-submission"
|
||||||
assessment.url = f"/assignment-submission/{assessment.assessment_name}/{submission_name}"
|
assessment.url = f"/lms/assignment-submission/{assessment.assessment_name}/{submission_name}"
|
||||||
|
|
||||||
return assessment
|
return assessment
|
||||||
|
|
||||||
|
|||||||
@@ -66,4 +66,4 @@ indent-style = "tab"
|
|||||||
docstring-code-format = true
|
docstring-code-format = true
|
||||||
|
|
||||||
[tool.bench.frappe-dependencies]
|
[tool.bench.frappe-dependencies]
|
||||||
frappe = ">=15.0.0,<17.0.0"
|
frappe = ">=15.0.0,<=17.0.0-dev"
|
||||||
Reference in New Issue
Block a user