fix: misc ui issues
This commit is contained in:
@@ -125,7 +125,7 @@
|
|||||||
@click="redirectToWebsite()"
|
@click="redirectToWebsite()"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :text="__('Help')">
|
<Tooltip v-if="showOnboarding" :text="__('Help')">
|
||||||
<CircleHelp
|
<CircleHelp
|
||||||
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
|
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
|
||||||
@click="
|
@click="
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div v-if="batch.data" class="">
|
||||||
<div class="w-full flex items-center justify-between pb-4">
|
<div class="w-full flex items-center justify-between pb-4">
|
||||||
<div class="font-medium text-ink-gray-7">
|
<div class="font-medium text-ink-gray-7">
|
||||||
{{ __('Statistics') }}
|
{{ __('Statistics') }}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="font-semibold">
|
<span class="font-semibold">
|
||||||
{{ batch.courses?.length }}
|
{{ batch.data.courses?.length }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{{ __('Courses') }}
|
{{ __('Courses') }}
|
||||||
@@ -201,9 +201,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StudentModal
|
<StudentModal
|
||||||
:batch="props.batch.name"
|
:batch="props.batch.data.name"
|
||||||
v-model="showStudentModal"
|
v-model="showStudentModal"
|
||||||
v-model:reloadStudents="students"
|
v-model:reloadStudents="students"
|
||||||
|
v-model:batchModal="props.batch"
|
||||||
/>
|
/>
|
||||||
<BatchStudentProgress
|
<BatchStudentProgress
|
||||||
:student="selectedStudent"
|
:student="selectedStudent"
|
||||||
@@ -258,15 +259,14 @@ const props = defineProps({
|
|||||||
|
|
||||||
const students = createResource({
|
const students = createResource({
|
||||||
url: 'lms.lms.utils.get_batch_students',
|
url: 'lms.lms.utils.get_batch_students',
|
||||||
cache: ['students', props.batch.name],
|
|
||||||
params: {
|
params: {
|
||||||
batch: props.batch?.name,
|
batch: props.batch?.data?.name,
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
chartData.value = getChartData()
|
chartData.value = getChartData()
|
||||||
showProgressChart.value =
|
showProgressChart.value =
|
||||||
data.length && (props.batch?.courses?.length || assessmentCount.value)
|
data.length && (props.batch?.data?.courses?.length || assessmentCount.value)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -323,6 +323,7 @@ const removeStudents = (selections, unselectAll) => {
|
|||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
students.reload()
|
students.reload()
|
||||||
|
props.batch.reload()
|
||||||
toast.success(__('Students deleted successfully'))
|
toast.success(__('Students deleted successfully'))
|
||||||
unselectAll()
|
unselectAll()
|
||||||
},
|
},
|
||||||
@@ -434,7 +435,7 @@ const certificationCount = createResource({
|
|||||||
params: {
|
params: {
|
||||||
doctype: 'LMS Certificate',
|
doctype: 'LMS Certificate',
|
||||||
filters: {
|
filters: {
|
||||||
batch_name: props.batch.name,
|
batch_name: props.batch?.data?.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="option.description"
|
v-if="option.description && option.description != option.label"
|
||||||
class="text-xs text-ink-gray-7"
|
class="text-xs text-ink-gray-7"
|
||||||
v-html="option.description"
|
v-html="option.description"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="mb-1.5 text-sm text-ink-gray-5">
|
<div class="mb-1.5 text-sm text-ink-gray-5">
|
||||||
{{ __('Announcement') }}
|
{{ __('Announcement') }}
|
||||||
|
<span class="text-ink-red-3">*</span>
|
||||||
</div>
|
</div>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
:fixedMenu="true"
|
:fixedMenu="true"
|
||||||
@@ -86,10 +87,13 @@ const makeAnnouncement = (close) => {
|
|||||||
{
|
{
|
||||||
validate() {
|
validate() {
|
||||||
if (!props.students.length) {
|
if (!props.students.length) {
|
||||||
return 'No students in this batch'
|
return __('No students in this batch')
|
||||||
}
|
}
|
||||||
if (!announcement.subject) {
|
if (!announcement.subject) {
|
||||||
return 'Subject is required'
|
return __('Subject is required')
|
||||||
|
}
|
||||||
|
if (!announcement.announcement) {
|
||||||
|
return __('Announcement is required')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|||||||
@@ -25,6 +25,21 @@
|
|||||||
v-model="assessment"
|
v-model="assessment"
|
||||||
:doctype="assessmentType"
|
:doctype="assessmentType"
|
||||||
:label="__('Assessment')"
|
:label="__('Assessment')"
|
||||||
|
:onCreate="(value, close) => {
|
||||||
|
close()
|
||||||
|
if (assessmentType === 'LMS Quiz') {
|
||||||
|
router.push({
|
||||||
|
name: 'QuizForm',
|
||||||
|
params: {
|
||||||
|
quizID: 'new',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (assessmentType === 'LMS Assignment') {
|
||||||
|
router.push({
|
||||||
|
name: 'Assignments'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,11 +49,13 @@
|
|||||||
import { Dialog, FormControl, createResource, toast } from 'frappe-ui'
|
import { Dialog, FormControl, createResource, toast } from 'frappe-ui'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const assessmentType = ref(null)
|
const assessmentType = ref(null)
|
||||||
const assessment = ref(null)
|
const assessment = ref(null)
|
||||||
const assessments = defineModel('assessments')
|
const assessments = defineModel('assessments')
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batch: {
|
batch: {
|
||||||
|
|||||||
@@ -19,12 +19,21 @@
|
|||||||
v-model="course"
|
v-model="course"
|
||||||
:label="__('Course')"
|
:label="__('Course')"
|
||||||
:required="true"
|
:required="true"
|
||||||
|
:onCreate="(value, close) => {
|
||||||
|
close()
|
||||||
|
router.push({
|
||||||
|
name: 'CourseForm',
|
||||||
|
params: {
|
||||||
|
courseName: 'new'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
doctype="Course Evaluator"
|
doctype="Course Evaluator"
|
||||||
v-model="evaluator"
|
v-model="evaluator"
|
||||||
:label="__('Evaluator')"
|
:label="__('Evaluator')"
|
||||||
:onCreate="(value, close) => openSettings(close)"
|
:onCreate="(value, close) => openSettings('Evaluators', close)"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,15 +44,16 @@ import { Dialog, createResource, toast } from 'frappe-ui'
|
|||||||
import { ref, inject } from 'vue'
|
import { ref, inject } from 'vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { useOnboarding } from 'frappe-ui/frappe'
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { openSettings } from '@/utils'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const course = ref(null)
|
const course = ref(null)
|
||||||
const evaluator = ref(null)
|
const evaluator = ref(null)
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const courses = defineModel('courses')
|
const courses = defineModel('courses')
|
||||||
|
const router = useRouter()
|
||||||
const { updateOnboardingStep } = useOnboarding('learning')
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const settingsStore = useSettings()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batch: {
|
batch: {
|
||||||
@@ -87,10 +97,4 @@ const addCourse = (close) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openSettings = (close) => {
|
|
||||||
close()
|
|
||||||
settingsStore.activeTab = 'Evaluators'
|
|
||||||
settingsStore.isSettingsOpen = true
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="text-xl font-semibold">
|
<div class="text-xl font-semibold">
|
||||||
{{ student.full_name }}
|
{{ student.full_name }}
|
||||||
</div>
|
</div>
|
||||||
<Badge :theme="student.progress === 100 ? 'green' : 'red'">
|
<Badge v-if="Object.keys(student.assessments).length || Object.keys(student.courses).length" :theme="student.progress === 100 ? 'green' : 'red'">
|
||||||
{{ student.progress }}% {{ __('Complete') }}
|
{{ student.progress }}% {{ __('Complete') }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- Assessments -->
|
<!-- Assessments -->
|
||||||
<div class="space-y-2 text-sm">
|
<div v-if="Object.keys(student.assessments).length" class="space-y-2 text-sm">
|
||||||
<div class="flex items-center border-b pb-1 font-medium">
|
<div class="flex items-center border-b pb-1 font-medium">
|
||||||
<span class="flex-1">
|
<span class="flex-1">
|
||||||
{{ __('Assessment') }}
|
{{ __('Assessment') }}
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Courses -->
|
<!-- Courses -->
|
||||||
<div class="space-y-2 text-sm">
|
<div v-if="Object.keys(student.courses).length" class="space-y-2 text-sm">
|
||||||
<div class="flex items-center border-b pb-1 font-medium">
|
<div class="flex items-center border-b pb-1 font-medium">
|
||||||
<span class="flex-1">
|
<span class="flex-1">
|
||||||
{{ __('Courses') }}
|
{{ __('Courses') }}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
doctype="User"
|
doctype="User"
|
||||||
v-model="student"
|
v-model="student"
|
||||||
:filters="{ ignore_user_type: 1 }"
|
:filters="{ ignore_user_type: 1 }"
|
||||||
|
:onCreate="(value, close) => {
|
||||||
|
openSettings('Members', close)
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -29,8 +32,10 @@ import { Dialog, createResource, toast } from 'frappe-ui'
|
|||||||
import { ref, inject } from 'vue'
|
import { ref, inject } from 'vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { useOnboarding } from 'frappe-ui/frappe'
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
import { openSettings } from '@/utils'
|
||||||
|
|
||||||
const students = defineModel('reloadStudents')
|
const students = defineModel('reloadStudents')
|
||||||
|
const batchModal = defineModel('batchModal')
|
||||||
const student = ref()
|
const student = ref()
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const { updateOnboardingStep } = useOnboarding('learning')
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
@@ -65,6 +70,7 @@ const addStudent = (close) => {
|
|||||||
updateOnboardingStep('add_batch_student')
|
updateOnboardingStep('add_batch_student')
|
||||||
|
|
||||||
students.value.reload()
|
students.value.reload()
|
||||||
|
batchModal.value.reload()
|
||||||
student.value = null
|
student.value = null
|
||||||
close()
|
close()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<BatchDashboard :batch="batch" :isStudent="isStudent" />
|
<BatchDashboard :batch="batch" :isStudent="isStudent" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab.label == 'Dashboard'">
|
<div v-else-if="tab.label == 'Dashboard'">
|
||||||
<BatchStudents :batch="batch.data" />
|
<BatchStudents :batch="batch" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab.label == 'Classes'">
|
<div v-else-if="tab.label == 'Classes'">
|
||||||
<LiveClass :batch="batch.data.name" />
|
<LiveClass :batch="batch.data.name" />
|
||||||
@@ -357,7 +357,11 @@ watch(tabIndex, () => {
|
|||||||
|
|
||||||
const canMakeAnnouncement = () => {
|
const canMakeAnnouncement = () => {
|
||||||
if (readOnlyMode) return false
|
if (readOnlyMode) return false
|
||||||
if (batch.data) return user.data?.is_moderator || user.data?.is_evaluator
|
|
||||||
|
if (!batch.data?.students?.length)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return user.data?.is_moderator || user.data?.is_evaluator
|
||||||
}
|
}
|
||||||
|
|
||||||
usePageMeta(() => {
|
usePageMeta(() => {
|
||||||
|
|||||||
@@ -285,15 +285,14 @@ import { Image } from 'lucide-vue-next'
|
|||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { useOnboarding } from 'frappe-ui/frappe'
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
import { useSettings } from '@/stores/settings'
|
|
||||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import { openSettings } from '@/utils'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const { updateOnboardingStep } = useOnboarding('learning')
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const settingsStore = useSettings()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batchName: {
|
batchName: {
|
||||||
@@ -500,11 +499,7 @@ const validateFile = (file) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openSettings = (category, close) => {
|
|
||||||
close()
|
|
||||||
settingsStore.activeTab = category
|
|
||||||
settingsStore.isSettingsOpen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let crumbs = [
|
let crumbs = [
|
||||||
|
|||||||
@@ -20,12 +20,14 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="p-5 pb-10">
|
<div class="p-5 pb-10">
|
||||||
<div
|
<div
|
||||||
|
v-if="batchCount"
|
||||||
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
|
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
|
||||||
>
|
>
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||||
{{ __('All Batches') }}
|
{{ __('All Batches') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
v-if="batches.data?.length || batchCount"
|
||||||
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
|
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
|
||||||
>
|
>
|
||||||
<TabButtons
|
<TabButtons
|
||||||
@@ -86,6 +88,7 @@
|
|||||||
import {
|
import {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Button,
|
Button,
|
||||||
|
call,
|
||||||
createListResource,
|
createListResource,
|
||||||
FormControl,
|
FormControl,
|
||||||
Select,
|
Select,
|
||||||
@@ -93,7 +96,7 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, onMounted, ref, watch } from 'vue'
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { BookOpen, Plus } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import BatchCard from '@/components/BatchCard.vue'
|
import BatchCard from '@/components/BatchCard.vue'
|
||||||
import EmptyState from '@/components/EmptyState.vue'
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
@@ -112,10 +115,12 @@ const is_student = computed(() => user.data?.is_student)
|
|||||||
const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
|
const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
|
||||||
const orderBy = ref('start_date')
|
const orderBy = ref('start_date')
|
||||||
const readOnlyMode = window.read_only_mode
|
const readOnlyMode = window.read_only_mode
|
||||||
|
const batchCount = ref(0)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setFiltersFromQuery()
|
setFiltersFromQuery()
|
||||||
updateBatches()
|
updateBatches()
|
||||||
|
getBatchCount()
|
||||||
categories.value = [
|
categories.value = [
|
||||||
{
|
{
|
||||||
label: '',
|
label: '',
|
||||||
@@ -293,6 +298,14 @@ const canCreateBatch = () => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getBatchCount = () => {
|
||||||
|
call('frappe.client.get_count', {
|
||||||
|
doctype: 'LMS Batch',
|
||||||
|
}).then((data) => {
|
||||||
|
batchCount.value = data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const breadcrumbs = computed(() => [
|
const breadcrumbs = computed(() => [
|
||||||
{
|
{
|
||||||
label: __('Batches'),
|
label: __('Batches'),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="sticky flex items-center justify-between top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
class="sticky flex items-center justify-between top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
||||||
>
|
>
|
||||||
<Breadcrumbs :items="breadcrumbs" />
|
<Breadcrumbs :items="breadcrumbs" />
|
||||||
<router-link :to="{ name: 'Batches' }">
|
<router-link :to="{ name: 'Batches', query: { 'certification': true } }">
|
||||||
<Button>
|
<Button>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<GraduationCap class="h-4 w-4 stroke-1.5" />
|
<GraduationCap class="h-4 w-4 stroke-1.5" />
|
||||||
|
|||||||
@@ -282,12 +282,12 @@ import {
|
|||||||
watch,
|
watch,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { Check, Image, Trash2, X } from 'lucide-vue-next'
|
import { Image, Trash2, X } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { useOnboarding } from 'frappe-ui/frappe'
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { openSettings } from '@/utils'
|
||||||
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'
|
||||||
@@ -297,7 +297,6 @@ const newTag = ref('')
|
|||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const instructors = ref([])
|
const instructors = ref([])
|
||||||
const settingsStore = useSettings()
|
|
||||||
const app = getCurrentInstance()
|
const app = getCurrentInstance()
|
||||||
const { updateOnboardingStep } = useOnboarding('learning')
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const { $dialog } = app.appContext.config.globalProperties
|
const { $dialog } = app.appContext.config.globalProperties
|
||||||
@@ -566,12 +565,6 @@ const check_permission = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openSettings = (category, close) => {
|
|
||||||
close()
|
|
||||||
settingsStore.activeTab = category
|
|
||||||
settingsStore.isSettingsOpen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
let crumbs = [
|
let crumbs = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,12 +20,14 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="p-5 pb-10">
|
<div class="p-5 pb-10">
|
||||||
<div
|
<div
|
||||||
|
v-if="courseCount"
|
||||||
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
|
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
|
||||||
>
|
>
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||||
{{ __('All Courses') }}
|
{{ __('All Courses') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
v-if="courses.data?.length || courseCount"
|
||||||
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
|
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
|
||||||
>
|
>
|
||||||
<TabButtons :buttons="courseTabs" v-model="currentTab" />
|
<TabButtons :buttons="courseTabs" v-model="currentTab" />
|
||||||
@@ -89,7 +91,7 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, onMounted, ref, watch } from 'vue'
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { BookOpen, Plus } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { canCreateCourse } from '@/utils'
|
import { canCreateCourse } from '@/utils'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
@@ -107,12 +109,13 @@ const certification = ref(false)
|
|||||||
const filters = ref({})
|
const filters = ref({})
|
||||||
const currentTab = ref('Live')
|
const currentTab = ref('Live')
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const readOnlyMode = window.read_only_mode
|
const courseCount = ref(0)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
identifyUserPersona()
|
identifyUserPersona()
|
||||||
setFiltersFromQuery()
|
setFiltersFromQuery()
|
||||||
updateCourses()
|
updateCourses()
|
||||||
|
getCourseCount()
|
||||||
categories.value = [
|
categories.value = [
|
||||||
{
|
{
|
||||||
label: '',
|
label: '',
|
||||||
@@ -161,19 +164,22 @@ const identifyUserPersona = async () => {
|
|||||||
if (user.data?.is_system_manager && !user.data?.developer_mode) {
|
if (user.data?.is_system_manager && !user.data?.developer_mode) {
|
||||||
let personaCaptured = await isPersonaCaptured()
|
let personaCaptured = await isPersonaCaptured()
|
||||||
if (personaCaptured) return
|
if (personaCaptured) return
|
||||||
|
if (!courseCount.value) {
|
||||||
call('frappe.client.get_count', {
|
|
||||||
doctype: 'LMS Course',
|
|
||||||
}).then((data) => {
|
|
||||||
if (!data) {
|
|
||||||
router.push({
|
router.push({
|
||||||
name: 'PersonaForm',
|
name: 'PersonaForm',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCourseCount = () => {
|
||||||
|
call('frappe.client.get_count', {
|
||||||
|
doctype: 'LMS Course',
|
||||||
|
}).then((data) => {
|
||||||
|
courseCount.value = data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const updateCourses = () => {
|
const updateCourses = () => {
|
||||||
updateFilters()
|
updateFilters()
|
||||||
courses.update({
|
courses.update({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</header>
|
</header>
|
||||||
<div class="py-5">
|
<div class="py-5">
|
||||||
<div class="container border-b mb-4 pb-4">
|
<div class="container border-b mb-4 pb-5">
|
||||||
<div class="text-lg font-semibold mb-4">
|
<div class="text-lg font-semibold mb-4">
|
||||||
{{ __('Job Details') }}
|
{{ __('Job Details') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -20,6 +20,15 @@
|
|||||||
:label="__('Title')"
|
:label="__('Title')"
|
||||||
:required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-model="job.type"
|
||||||
|
:label="__('Type')"
|
||||||
|
type="select"
|
||||||
|
:options="jobTypes"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="job.location"
|
v-model="job.location"
|
||||||
:label="__('City')"
|
:label="__('City')"
|
||||||
@@ -31,17 +40,8 @@
|
|||||||
:label="__('Country')"
|
:label="__('Country')"
|
||||||
:required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<FormControl
|
|
||||||
v-model="job.type"
|
|
||||||
:label="__('Type')"
|
|
||||||
type="select"
|
|
||||||
:options="jobTypes"
|
|
||||||
class="mb-4"
|
|
||||||
:required="true"
|
|
||||||
/>
|
|
||||||
<FormControl
|
<FormControl
|
||||||
|
v-if="jobName != 'new'"
|
||||||
v-model="job.status"
|
v-model="job.status"
|
||||||
:label="__('Status')"
|
:label="__('Status')"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container border-b mb-4 pb-4">
|
<div class="container border-b mb-4 pb-5">
|
||||||
<div class="text-lg font-semibold mb-4">
|
<div class="text-lg font-semibold mb-4">
|
||||||
{{ __('Company Details') }}
|
{{ __('Company Details') }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,10 +26,10 @@
|
|||||||
</header>
|
</header>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
v-if="jobCount"
|
||||||
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between w-full md:w-4/5 mx-auto p-5"
|
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between w-full md:w-4/5 mx-auto p-5"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="jobCount"
|
|
||||||
class="text-xl font-semibold text-ink-gray-7 mb-4 md:mb-0"
|
class="text-xl font-semibold text-ink-gray-7 mb-4 md:mb-0"
|
||||||
>
|
>
|
||||||
{{ __('{0} Open Jobs').format(jobCount) }}
|
{{ __('{0} Open Jobs').format(jobCount) }}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ import { computed, ref } from 'vue'
|
|||||||
import { Plus, Trash2 } from 'lucide-vue-next'
|
import { Plus, Trash2 } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { openSettings } from '@/utils'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
|
||||||
@@ -204,7 +204,6 @@ const currentForm = ref(null)
|
|||||||
const course = ref(null)
|
const course = ref(null)
|
||||||
const member = ref(null)
|
const member = ref(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const settingsStore = useSettings()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
programName: {
|
programName: {
|
||||||
@@ -359,12 +358,6 @@ const memberColumns = computed(() => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const openSettings = (category, close) => {
|
|
||||||
close()
|
|
||||||
settingsStore.activeTab = category
|
|
||||||
settingsStore.isSettingsOpen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadbrumbs = computed(() => {
|
const breadbrumbs = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Quiz } from '@/utils/quiz'
|
|||||||
import { Assignment } from '@/utils/assignment'
|
import { Assignment } from '@/utils/assignment'
|
||||||
import { Upload } from '@/utils/upload'
|
import { Upload } from '@/utils/upload'
|
||||||
import { Markdown } from '@/utils/markdownParser'
|
import { Markdown } from '@/utils/markdownParser'
|
||||||
|
import { useSettings } from '@/stores/settings'
|
||||||
|
import { usersStore } from '@/stores/user'
|
||||||
import Header from '@editorjs/header'
|
import Header from '@editorjs/header'
|
||||||
import Paragraph from '@editorjs/paragraph'
|
import Paragraph from '@editorjs/paragraph'
|
||||||
import { CodeBox } from '@/utils/code'
|
import { CodeBox } from '@/utils/code'
|
||||||
@@ -13,7 +15,6 @@ import dayjs from '@/utils/dayjs'
|
|||||||
import Embed from '@editorjs/embed'
|
import Embed from '@editorjs/embed'
|
||||||
import SimpleImage from '@editorjs/simple-image'
|
import SimpleImage from '@editorjs/simple-image'
|
||||||
import Table from '@editorjs/table'
|
import Table from '@editorjs/table'
|
||||||
import { usersStore } from '../stores/user'
|
|
||||||
import Plyr from 'plyr'
|
import Plyr from 'plyr'
|
||||||
import 'plyr/dist/plyr.css'
|
import 'plyr/dist/plyr.css'
|
||||||
|
|
||||||
@@ -551,3 +552,10 @@ export const enablePlyr = () => {
|
|||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const openSettings = (category, close) => {
|
||||||
|
const settingsStore = useSettings()
|
||||||
|
close()
|
||||||
|
settingsStore.activeTab = category
|
||||||
|
settingsStore.isSettingsOpen = true
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ class LMSBatch(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_seats_left()
|
self.validate_seats_left()
|
||||||
self.validate_batch_end_date()
|
self.validate_batch_end_date()
|
||||||
|
self.validate_batch_time()
|
||||||
self.validate_duplicate_courses()
|
self.validate_duplicate_courses()
|
||||||
self.validate_payments_app()
|
self.validate_payments_app()
|
||||||
self.validate_amount_and_currency()
|
self.validate_amount_and_currency()
|
||||||
@@ -39,6 +40,11 @@ class LMSBatch(Document):
|
|||||||
if self.end_date < self.start_date:
|
if self.end_date < self.start_date:
|
||||||
frappe.throw(_("Batch end date cannot be before the batch start date"))
|
frappe.throw(_("Batch end date cannot be before the batch start date"))
|
||||||
|
|
||||||
|
def validate_batch_time(self):
|
||||||
|
if self.start_time and self.end_time:
|
||||||
|
if get_time(self.start_time) >= get_time(self.end_time):
|
||||||
|
frappe.throw(_("Batch start time cannot be greater than or equal to end time."))
|
||||||
|
|
||||||
def validate_duplicate_courses(self):
|
def validate_duplicate_courses(self):
|
||||||
courses = [row.course for row in self.courses]
|
courses = [row.course for row in self.courses]
|
||||||
duplicates = {course for course in courses if courses.count(course) > 1}
|
duplicates = {course for course in courses if courses.count(course) > 1}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@
|
|||||||
<b>{{ _("Batch Start Date:") }}</b> {{ frappe.utils.format_date(start_date, "medium") }}
|
<b>{{ _("Batch Start Date:") }}</b> {{ frappe.utils.format_date(start_date, "medium") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{% if medium %}
|
||||||
<p>
|
<p>
|
||||||
<b>{{ _("Medium:") }}</b> {{ medium }}
|
<b>{{ _("Medium:") }}</b> {{ medium }}
|
||||||
</p>
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>{{ _("Timings:") }}</b> {{ frappe.utils.format_time(start_time, "hh:mm a") }}
|
<b>{{ _("Timings:") }}</b> {{ frappe.utils.format_time(start_time, "hh:mm a") }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "frappe_lms",
|
"name": "frappe_lms",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Easy to use, open-source, Learning Management System",
|
"description": "Easy to use, open-source, Learning Management System",
|
||||||
"workspaces1": [
|
"workspaces": [
|
||||||
"frappe-ui",
|
"frappe-ui",
|
||||||
"frontend"
|
"frontend"
|
||||||
],
|
],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user