chore: resolved conflicts

This commit is contained in:
Jannat Patel
2026-01-15 09:41:48 +05:30
18 changed files with 169 additions and 215 deletions
@@ -70,7 +70,7 @@ const props = withDefaults(
modelValue: string
label?: string
description?: string
type: 'image' | 'video'
type?: 'image' | 'video'
required?: boolean
}>(),
{
@@ -285,9 +285,6 @@ const certificate = createResource({
}&format=${encodeURIComponent(data.template)}`,
'_blank'
)
capture('certificate_issued', {
course: props.course.data.name,
})
},
})
@@ -52,14 +52,12 @@ import {
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import SettingFields from '@/components/Settings/SettingFields.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const show = defineModel<boolean>({ required: true, default: false })
const paymentGateways = defineModel<any>('paymentGateways')
const newGateway = ref(null)
const newGatewayFields = ref([])
const newGatewayData = ref<Record<string, any>>({})
const { capture } = useTelemetry()
const props = defineProps<{
gatewayID: string | null
@@ -160,7 +158,6 @@ const saveNewGateway = (close: () => void) => {
...newGatewayData.value,
},
}).then((data: any) => {
capture('payment_gateway_configured')
paymentGateways.value.reload()
close()
})
@@ -4,15 +4,30 @@
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('Upcoming Evaluations') }}
</div>
<Button
v-if="
upcoming_evals.data?.length != evaluationCourses.length && !forHome
"
@click="openEvalModal"
>
<Button v-if="canScheduleEvals" @click="openEvalModal">
{{ __('Schedule Evaluation') }}
</Button>
</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
class="grid gap-4"
@@ -99,7 +114,7 @@
</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.') }}
</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) => {
$dialog({
title: __('Cancel this evaluation?'),
+1 -14
View File
@@ -144,20 +144,7 @@
</span>
</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 class="text-ink-gray-7 font-semibold mb-2">
{{ __('Feedback') }}
+7 -8
View File
@@ -281,21 +281,13 @@ import {
import {
Breadcrumbs,
FormControl,
FileUploader,
Button,
TextEditor,
createResource,
usePageMeta,
toast,
call,
Toast,
} 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 {
escapeHTML,
getMetaInfo,
@@ -303,7 +295,13 @@ import {
sanitizeHTML,
updateMetaInfo,
} 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 MultiSelect from '@/components/Controls/MultiSelect.vue'
import Link from '@/components/Controls/Link.vue'
const router = useRouter()
const user = inject('$user')
@@ -466,6 +464,7 @@ const validateFields = () => {
!['description', 'batch_details'].includes(key) &&
typeof batch[key] === 'string'
) {
console.log(key)
batch[key] = escapeHTML(batch[key])
}
})
+13 -87
View File
@@ -75,58 +75,11 @@
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<div class="mb-4">
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Course Image') }}
</div>
<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>
<Uploader
v-model="course.image"
:label="__('Course Image')"
:required="false"
/>
<ColorSwatches
v-model="course.card_gradient"
@@ -333,7 +286,6 @@ import {
Button,
createResource,
FormControl,
FileUploader,
usePageMeta,
toast,
} from 'frappe-ui'
@@ -347,22 +299,22 @@ import {
watch,
getCurrentInstance,
} 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 {
escapeHTML,
getMetaInfo,
openSettings,
sanitizeHTML,
updateMetaInfo,
validateFile,
} 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 CourseOutline from '@/components/CourseOutline.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import ColorSwatches from '@/components/Controls/ColorSwatches.vue'
import Uploader from '@/components/Controls/Uploader.vue'
const user = inject('$user')
const newTag = ref('')
@@ -386,7 +338,7 @@ const course = reactive({
short_introduction: '',
description: '',
video_link: '',
course_image: null,
image: null,
card_gradient: '',
tags: '',
category: '',
@@ -448,7 +400,7 @@ const courseCreationResource = createResource({
return {
doc: {
doctype: 'LMS Course',
image: course.course_image?.file_url || '',
image: course.image,
instructors: instructors.value.map((instructor) => ({
instructor: instructor,
})),
@@ -469,7 +421,7 @@ const courseEditResource = createResource({
doctype: 'LMS Course',
name: values.course,
fieldname: {
image: course.course_image?.file_url || '',
image: course.image,
instructors: instructors.value.map((instructor) => ({
instructor: instructor,
})),
@@ -519,24 +471,10 @@ const courseResource = createResource({
course[key] = course[key] ? true : false
}
if (data.image) imageResource.reload({ image: data.image })
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 = () => {
course.description = sanitizeHTML(course.description)
@@ -580,16 +518,12 @@ const createCourse = () => {
}
const editCourse = () => {
let was_published = courseResource.data.published
courseEditResource.submit(
{
course: courseResource.data.name,
},
{
onSuccess() {
if (!was_published && course.published) {
capture('publish_course')
}
updateMetaInfo('courses', props.courseName, meta)
toast.success(__('Course updated successfully'))
},
@@ -657,14 +591,6 @@ const removeTag = (tag) => {
newTag.value = ''
}
const saveImage = (file) => {
course.course_image = file
}
const removeImage = () => {
course.course_image = null
}
const check_permission = () => {
let user_is_instructor = false
if (user.data?.is_moderator) return
+10 -69
View File
@@ -83,47 +83,11 @@
class="mb-4"
:required="true"
/>
<label class="block text-ink-gray-5 text-xs mb-1 mt-4">
{{ __('Company Logo') }}
<span class="text-ink-red-3">*</span>
</label>
<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>
<Uploader
v-model="job.company_logo"
:label="__('Company Logo')"
:required="false"
/>
</div>
</div>
</div>
@@ -150,15 +114,14 @@ import {
createResource,
Button,
TextEditor,
FileUploader,
usePageMeta,
toast,
} from 'frappe-ui'
import { computed, onMounted, reactive, inject } from 'vue'
import { FileText, X } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
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 router = useRouter()
@@ -177,7 +140,7 @@ const newJob = createResource({
return {
doc: {
doctype: 'Job Opportunity',
company_logo: job.image?.file_url,
company_logo: job.company_logo,
...job,
},
}
@@ -191,7 +154,7 @@ const updateJob = createResource({
doctype: 'Job Opportunity',
name: props.jobName,
fieldname: {
company_logo: job.image.file_url,
company_logo: job.company_logo,
...job,
},
}
@@ -215,20 +178,6 @@ const jobDetail = createResource({
Object.keys(data).forEach((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',
company_name: '',
company_website: '',
image: null,
company_logo: null,
description: '',
company_email_address: '',
})
@@ -322,14 +271,6 @@ const validateJobFields = () => {
})
}
const saveImage = (file) => {
job.image = file
}
const removeImage = () => {
job.image = null
}
const jobTypes = computed(() => {
return [
{ label: 'Full Time', value: 'Full Time' },
+9
View File
@@ -665,6 +665,15 @@ iframe {
padding: 8px;
}
.codex-editor--narrow .ce-toolbox .ce-popover {
right: unset;
left: initial;
}
.ce-popover {
border-radius: 12px;
}
.cdx-search-field {
border: none;
}
+3 -10
View File
@@ -233,7 +233,6 @@ import { ClipboardList, ListChecks, Plus, Trash2 } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { escapeHTML } from '@/utils'
import Question from '@/components/Modals/Question.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const { brand } = sessionStore()
const showQuestionModal = ref(false)
@@ -242,7 +241,6 @@ const currentQuestion = reactive({
marks: 0,
name: '',
})
const { capture } = useTelemetry()
const user = inject('$user')
const router = useRouter()
const readOnlyMode = window.read_only_mode
@@ -260,9 +258,7 @@ onMounted(() => {
if (!user.data?.is_moderator && !user.data?.is_instructor) {
router.push({ name: 'Courses' })
}
if (props.quizID !== 'new') {
quizDetails.reload()
}
quizDetails.reload()
window.addEventListener('keydown', keyboardShortcut)
})
@@ -310,9 +306,6 @@ const submitQuiz = () => {
},
{
onSuccess(data) {
if (props.quizID === 'new') {
capture('quiz_created')
}
quizDetails.doc.total_marks = data.total_marks
toast.success(__('Quiz updated successfully'))
},
@@ -408,7 +401,7 @@ const breadcrumbs = computed(() => {
]
crumbs.push({
label: props.quizID == 'new' ? __('New Quiz') : quizDetails.doc?.title,
label: quizDetails.doc?.title,
route: { name: 'QuizForm', params: { quizID: props.quizID } },
})
return crumbs
@@ -416,7 +409,7 @@ const breadcrumbs = computed(() => {
usePageMeta(() => {
return {
title: props.quizID == 'new' ? __('New Quiz') : quizDetails.doc?.title,
title: quizDetails.doc?.title,
icon: brand.favicon,
}
})
+3 -3
View File
@@ -1896,9 +1896,9 @@
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
"@types/node@*":
version "25.0.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.7.tgz#afd2f0d34358631a287c6726dabe0c845abc2bae"
integrity sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==
version "25.0.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.8.tgz#e54e00f94fe1db2497b3e42d292b8376a2678c8d"
integrity sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==
dependencies:
undici-types "~7.16.0"
@@ -73,7 +73,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-12-04 12:53:38.246250",
"modified": "2026-01-14 08:53:16.672825",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Batch Enrollment",
@@ -108,6 +108,18 @@
"if_owner": 1,
"read": 1,
"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",
@@ -76,7 +76,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-05-21 15:58:51.667270",
"modified": "2026-01-14 08:53:38.088168",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Batch Feedback",
@@ -105,6 +105,30 @@
"role": "LMS Student",
"share": 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",
@@ -177,7 +177,7 @@
"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",
"module": "LMS",
"name": "LMS Live Class",
@@ -215,6 +215,18 @@
"report": 1,
"role": "LMS Student",
"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",
@@ -155,11 +155,17 @@ def create_attendance(live_class, data):
doc = frappe.new_doc("LMS Live Class Participant")
doc.live_class = live_class.name
doc.member = participant.get("user_email")
doc.joined_at = participant.get("join_time")
doc.left_at = participant.get("leave_time")
doc.duration = participant.get("duration")
doc.joined_at = get_datetime(participant.get("join_time"))
doc.left_at = get_datetime(participant.get("leave_time"))
doc.duration = get_minutes(participant.get("duration"))
doc.insert()
def update_attendees_count(live_class, 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,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-05-27 22:32:24.196643",
"modified_by": "Administrator",
"modified": "2026-01-14 08:54:21.421321",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Live Class Participant",
"owner": "Administrator",
@@ -106,6 +106,30 @@
"role": "System Manager",
"share": 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",
+1 -1
View File
@@ -1293,7 +1293,7 @@ def get_assignment_details(assessment, member):
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
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
+1 -1
View File
@@ -66,4 +66,4 @@ indent-style = "tab"
docstring-code-format = true
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<17.0.0"
frappe = ">=15.0.0,<=17.0.0-dev"