Merge pull request #1357 from pateljannat/course-certification-filter
refactor: course list fetching and filters
This commit is contained in:
@@ -16,7 +16,8 @@
|
|||||||
{{ __('Featured') }}
|
{{ __('Featured') }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div
|
<div
|
||||||
v-for="tag in course.tags"
|
v-if="course.tags"
|
||||||
|
v-for="tag in course.tags?.split(', ')"
|
||||||
class="text-xs bg-white text-gray-800 px-2 py-0.5 rounded-md"
|
class="text-xs bg-white text-gray-800 px-2 py-0.5 rounded-md"
|
||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
|
|||||||
@@ -1,316 +1,325 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="courses.data">
|
<header
|
||||||
<header
|
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 top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
>
|
||||||
|
<Breadcrumbs :items="breadcrumbs" />
|
||||||
|
<router-link
|
||||||
|
v-if="user.data?.is_moderator"
|
||||||
|
:to="{
|
||||||
|
name: 'CourseForm',
|
||||||
|
params: { courseName: 'new' },
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<Breadcrumbs
|
<Button variant="solid">
|
||||||
class="h-7"
|
<template #prefix>
|
||||||
:items="[{ label: __('Courses'), route: { name: 'Courses' } }]"
|
<Plus class="h-4 w-4 stroke-1.5" />
|
||||||
/>
|
</template>
|
||||||
<div class="flex space-x-2 justify-end">
|
{{ __('New') }}
|
||||||
<div class="w-40 md:w-44">
|
</Button>
|
||||||
<FormControl
|
</router-link>
|
||||||
v-if="categories.data?.length"
|
</header>
|
||||||
type="select"
|
<div class="p-5 pb-10">
|
||||||
v-model="currentCategory"
|
<div
|
||||||
:options="categories.data"
|
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
|
||||||
:placeholder="__('Category')"
|
>
|
||||||
/>
|
<div class="text-lg font-semibold">
|
||||||
</div>
|
{{ __('All Courses') }}
|
||||||
<div class="w-28 md:w-36">
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
|
||||||
|
>
|
||||||
|
<TabButtons
|
||||||
|
v-if="user.data"
|
||||||
|
:buttons="courseTabs"
|
||||||
|
v-model="currentTab"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-model="certification"
|
||||||
|
:label="__('Certification')"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateCourses()"
|
||||||
|
/>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
|
v-model="title"
|
||||||
|
:placeholder="__('Search by Title')"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40"
|
||||||
v-model="searchQuery"
|
@input="updateCourses()"
|
||||||
@input="courses.reload()"
|
/>
|
||||||
>
|
<div class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40">
|
||||||
<template #prefix>
|
<Select
|
||||||
<Search
|
v-if="categories.length"
|
||||||
class="w-4 h-4 stroke-1.5 text-ink-gray-5"
|
v-model="currentCategory"
|
||||||
name="search"
|
:options="categories"
|
||||||
/>
|
:placeholder="__('Category')"
|
||||||
</template>
|
@change="updateCourses()"
|
||||||
</FormControl>
|
/>
|
||||||
</div>
|
|
||||||
<router-link
|
|
||||||
v-if="user.data?.is_moderator || user.data?.is_instructor"
|
|
||||||
:to="{
|
|
||||||
name: 'CourseForm',
|
|
||||||
params: {
|
|
||||||
courseName: 'new',
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<Button variant="solid">
|
|
||||||
<template #prefix>
|
|
||||||
<Plus class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
{{ __('New') }}
|
|
||||||
</Button>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="">
|
|
||||||
<Tabs
|
|
||||||
v-if="hasCourses"
|
|
||||||
as="div"
|
|
||||||
v-model="tabIndex"
|
|
||||||
tablistClass="overflow-x-visible flex-wrap !gap-3 md:flex-nowrap"
|
|
||||||
:tabs="makeTabs"
|
|
||||||
>
|
|
||||||
<template #tab="{ tab, selected }">
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="group -mb-px flex items-center gap-2 overflow-hidden border-b border-transparent py-2.5 text-base text-ink-gray-5 duration-300 ease-in-out hover:border-outline-gray-3 hover:text-ink-gray-9"
|
|
||||||
:class="{ 'text-ink-gray-9': selected }"
|
|
||||||
>
|
|
||||||
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
|
|
||||||
{{ __(tab.label) }}
|
|
||||||
<Badge theme="gray">
|
|
||||||
{{ tab.count }}
|
|
||||||
</Badge>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
<template #tab-panel="{ tab }">
|
|
||||||
<div
|
|
||||||
v-if="tab.courses && tab.courses.value.length"
|
|
||||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5 gap-7 my-5 mx-5"
|
|
||||||
>
|
|
||||||
<router-link
|
|
||||||
v-for="course in tab.courses.value"
|
|
||||||
:to="
|
|
||||||
course.membership && course.current_lesson
|
|
||||||
? {
|
|
||||||
name: 'Lesson',
|
|
||||||
params: {
|
|
||||||
courseName: course.name,
|
|
||||||
chapterNumber: course.current_lesson.split('-')[0],
|
|
||||||
lessonNumber: course.current_lesson.split('-')[1],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: course.membership
|
|
||||||
? {
|
|
||||||
name: 'Lesson',
|
|
||||||
params: {
|
|
||||||
courseName: course.name,
|
|
||||||
chapterNumber: 1,
|
|
||||||
lessonNumber: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: 'CourseDetail',
|
|
||||||
params: { courseName: course.name },
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<CourseCard :course="course" />
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<div v-else class="p-5 italic text-ink-gray-4">
|
|
||||||
{{ __('No {0} courses').format(tab.label.toLowerCase()) }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Tabs>
|
|
||||||
<div
|
|
||||||
v-else-if="
|
|
||||||
!courses.loading &&
|
|
||||||
(user.data?.is_moderator || user.data?.is_instructor)
|
|
||||||
"
|
|
||||||
class="grid grid-cols-3 p-5"
|
|
||||||
>
|
|
||||||
<router-link
|
|
||||||
:to="{
|
|
||||||
name: 'CourseForm',
|
|
||||||
params: {
|
|
||||||
courseName: 'new',
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="bg-surface-menu-bar py-32 px-5 rounded-md">
|
|
||||||
<div class="flex flex-col items-center text-center space-y-2">
|
|
||||||
<Plus
|
|
||||||
class="size-10 stroke-1 text-ink-gray-8 p-1 rounded-full border bg-surface-white"
|
|
||||||
/>
|
|
||||||
<div class="font-medium">
|
|
||||||
{{ __('Create a Course') }}
|
|
||||||
</div>
|
|
||||||
<span class="text-ink-gray-7 text-sm leading-4">
|
|
||||||
{{ __('You can add chapters and lessons to it.') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else-if="!courses.loading && !hasCourses"
|
|
||||||
class="text-center p-5 text-ink-gray-5 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-xl font-medium">
|
|
||||||
{{ __('No courses found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'There are no courses available at the moment. Keep an eye out, fresh learning experiences are on the way soon!'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="courses.data?.length"
|
||||||
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-5"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
v-for="course in courses.data"
|
||||||
|
:to="{ name: 'CourseDetail', params: { courseName: course.name } }"
|
||||||
|
>
|
||||||
|
<CourseCard :course="course" />
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="!courses.list.loading"
|
||||||
|
class="flex flex-col items-center justify-center text-sm text-ink-gray-5 italic mt-48"
|
||||||
|
>
|
||||||
|
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
||||||
|
<div class="text-lg font-medium mb-1">
|
||||||
|
{{ __('No courses found') }}
|
||||||
|
</div>
|
||||||
|
<div class="leading-5 w-2/5 text-center">
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'There are no courses matching the criteria. Keep an eye out, fresh learning experiences are on the way soon!'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!courses.list.loading && courses.hasNextPage"
|
||||||
|
class="flex justify-center mt-5"
|
||||||
|
>
|
||||||
|
<Button @click="courses.next()">
|
||||||
|
{{ __('Load More') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Button,
|
Button,
|
||||||
call,
|
createListResource,
|
||||||
createResource,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
Tabs,
|
Select,
|
||||||
|
TabButtons,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { BookOpen, Plus, Search } from 'lucide-vue-next'
|
import { BookOpen, Plus } from 'lucide-vue-next'
|
||||||
import { ref, computed, inject, onMounted, watch } from 'vue'
|
|
||||||
import { updateDocumentTitle } from '@/utils'
|
import { updateDocumentTitle } from '@/utils'
|
||||||
import { useRouter } from 'vue-router'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
import { useSettings } from '@/stores/settings'
|
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const searchQuery = ref('')
|
const dayjs = inject('$dayjs')
|
||||||
|
const start = ref(0)
|
||||||
|
const pageLength = ref(30)
|
||||||
|
const categories = ref([])
|
||||||
const currentCategory = ref(null)
|
const currentCategory = ref(null)
|
||||||
const hasCourses = ref(false)
|
const title = ref('')
|
||||||
const router = useRouter()
|
const certification = ref(false)
|
||||||
const settings = useSettings()
|
const filters = ref({})
|
||||||
|
const currentTab = ref('Live')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkLearningPath()
|
setFiltersFromQuery()
|
||||||
let queries = new URLSearchParams(location.search)
|
updateCourses()
|
||||||
if (queries.has('category')) {
|
categories.value = [
|
||||||
currentCategory.value = queries.get('category')
|
{
|
||||||
}
|
label: '',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkLearningPath = () => {
|
const setFiltersFromQuery = () => {
|
||||||
if (
|
let queries = new URLSearchParams(location.search)
|
||||||
settings.learningPaths.data &&
|
title.value = queries.get('title') || ''
|
||||||
(!user.data?.is_moderator || !user.data?.is_instructor)
|
currentCategory.value = queries.get('category') || null
|
||||||
) {
|
certification.value = queries.get('certification') || false
|
||||||
router.push({ name: 'Programs' })
|
}
|
||||||
|
|
||||||
|
const courses = createListResource({
|
||||||
|
doctype: 'LMS Course',
|
||||||
|
url: 'lms.lms.utils.get_courses',
|
||||||
|
cache: ['courses', user.data?.name],
|
||||||
|
pageLength: pageLength.value,
|
||||||
|
start: start.value,
|
||||||
|
onSuccess(data) {
|
||||||
|
let allCategories = data.map((course) => course.category)
|
||||||
|
allCategories = allCategories.filter(
|
||||||
|
(category, index) => allCategories.indexOf(category) === index && category
|
||||||
|
)
|
||||||
|
if (categories.value.length <= allCategories.length) {
|
||||||
|
updateCategories(data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateCourses = () => {
|
||||||
|
updateFilters()
|
||||||
|
courses.update({
|
||||||
|
filters: filters.value,
|
||||||
|
})
|
||||||
|
courses.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFilters = () => {
|
||||||
|
updateCategoryFilter()
|
||||||
|
updateTitleFilter()
|
||||||
|
updateCertificationFilter()
|
||||||
|
updateTabFilter()
|
||||||
|
updateStudentFilter()
|
||||||
|
setQueryParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCategoryFilter = () => {
|
||||||
|
if (currentCategory.value) {
|
||||||
|
filters.value['category'] = currentCategory.value
|
||||||
|
} else {
|
||||||
|
delete filters.value['category']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const courses = createResource({
|
const updateTitleFilter = () => {
|
||||||
url: 'lms.lms.utils.get_courses',
|
if (title.value) {
|
||||||
cache: ['courses', user.data?.email],
|
filters.value['title'] = ['like', `%${title.value}%`]
|
||||||
auto: true,
|
} else {
|
||||||
|
delete filters.value['title']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCertificationFilter = () => {
|
||||||
|
if (certification.value) {
|
||||||
|
filters.value['certification'] = 1
|
||||||
|
} else {
|
||||||
|
delete filters.value['certification']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTabFilter = () => {
|
||||||
|
if (!user.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete filters.value['live']
|
||||||
|
delete filters.value['created']
|
||||||
|
delete filters.value['published_on']
|
||||||
|
delete filters.value['upcoming']
|
||||||
|
|
||||||
|
if (currentTab.value == 'Enrolled' && user.data?.is_student) {
|
||||||
|
filters.value['enrolled'] = 1
|
||||||
|
delete filters.value['published']
|
||||||
|
} else {
|
||||||
|
delete filters.value['published']
|
||||||
|
delete filters.value['enrolled']
|
||||||
|
|
||||||
|
if (currentTab.value == 'Live') {
|
||||||
|
filters.value['published'] = 1
|
||||||
|
filters.value['upcoming'] = 0
|
||||||
|
filters.value['live'] = 1
|
||||||
|
} else if (currentTab.value == 'Upcoming') {
|
||||||
|
filters.value['upcoming'] = 1
|
||||||
|
filters.value['published'] = 1
|
||||||
|
} else if (currentTab.value == 'New') {
|
||||||
|
filters.value['published'] = 1
|
||||||
|
filters.value['published_on'] = [
|
||||||
|
'>=',
|
||||||
|
dayjs().add(-3, 'month').format('YYYY-MM-DD'),
|
||||||
|
]
|
||||||
|
} else if (currentTab.value == 'Created') {
|
||||||
|
filters.value['created'] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStudentFilter = () => {
|
||||||
|
if (!user.data || (user.data?.is_student && currentTab.value != 'Enrolled')) {
|
||||||
|
filters.value['published'] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setQueryParams = () => {
|
||||||
|
let queries = new URLSearchParams(location.search)
|
||||||
|
let filterKeys = {
|
||||||
|
title: title.value,
|
||||||
|
category: currentCategory.value,
|
||||||
|
certification: certification.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(filterKeys).forEach((key) => {
|
||||||
|
if (filterKeys[key]) {
|
||||||
|
queries.set(key, filterKeys[key])
|
||||||
|
} else {
|
||||||
|
queries.delete(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
history.replaceState({}, '', `${location.pathname}?${queries.toString()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCategories = (data) => {
|
||||||
|
data.forEach((course) => {
|
||||||
|
if (
|
||||||
|
course.category &&
|
||||||
|
!categories.value.find((category) => category.value === course.category)
|
||||||
|
)
|
||||||
|
categories.value.push({
|
||||||
|
label: course.category,
|
||||||
|
value: course.category,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(currentTab, () => {
|
||||||
|
updateCourses()
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabIndex = ref(0)
|
const courseType = computed(() => {
|
||||||
let tabs
|
let types = [
|
||||||
|
{ label: __(''), value: null },
|
||||||
|
{ label: __('New'), value: 'New' },
|
||||||
|
{ label: __('Upcoming'), value: 'Upcoming' },
|
||||||
|
]
|
||||||
|
if (user.data?.is_student) {
|
||||||
|
types.push({ label: __('Enrolled'), value: 'Enrolled' })
|
||||||
|
} else {
|
||||||
|
types.push({ label: __('Created'), value: 'Created' })
|
||||||
|
}
|
||||||
|
return types
|
||||||
|
})
|
||||||
|
|
||||||
const makeTabs = computed(() => {
|
const courseTabs = computed(() => {
|
||||||
tabs = []
|
let tabs = [
|
||||||
addToTabs('Live')
|
{
|
||||||
addToTabs('New')
|
label: __('Live'),
|
||||||
addToTabs('Upcoming')
|
},
|
||||||
|
{
|
||||||
if (user.data) {
|
label: __('New'),
|
||||||
addToTabs('Enrolled')
|
},
|
||||||
|
{
|
||||||
if (
|
label: __('Upcoming'),
|
||||||
user.data.is_moderator ||
|
},
|
||||||
user.data.is_instructor ||
|
]
|
||||||
courses.data?.created?.length
|
if (user.data?.is_student) {
|
||||||
) {
|
tabs.push({ label: __('Enrolled') })
|
||||||
addToTabs('Created')
|
} else {
|
||||||
}
|
tabs.push({ label: __('Created') })
|
||||||
|
|
||||||
if (user.data.is_moderator) {
|
|
||||||
addToTabs('Under Review')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return tabs
|
return tabs
|
||||||
})
|
})
|
||||||
|
|
||||||
const addToTabs = (label) => {
|
const breadcrumbs = computed(() => [
|
||||||
let courses = getCourses(label.toLowerCase().split(' ').join('_'))
|
{
|
||||||
tabs.push({
|
label: __('Courses'),
|
||||||
label,
|
route: { name: 'Courses' },
|
||||||
courses: computed(() => courses),
|
|
||||||
count: computed(() => courses.length),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCourses = (type) => {
|
|
||||||
let courseList = courses.data[type]
|
|
||||||
if (searchQuery.value) {
|
|
||||||
let query = searchQuery.value.toLowerCase()
|
|
||||||
courseList = courseList.filter(
|
|
||||||
(course) =>
|
|
||||||
course.title.toLowerCase().includes(query) ||
|
|
||||||
course.short_introduction.toLowerCase().includes(query) ||
|
|
||||||
course.tags.filter((tag) => tag.toLowerCase().includes(query)).length
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (currentCategory.value && currentCategory.value != '') {
|
|
||||||
courseList = courseList.filter(
|
|
||||||
(course) => course.category == currentCategory.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return courseList
|
|
||||||
}
|
|
||||||
|
|
||||||
const categories = createResource({
|
|
||||||
url: 'lms.lms.api.get_categories',
|
|
||||||
makeParams() {
|
|
||||||
return {
|
|
||||||
doctype: 'LMS Course',
|
|
||||||
filters: {
|
|
||||||
published: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
cache: ['courseCategories'],
|
])
|
||||||
auto: true,
|
|
||||||
transform(data) {
|
|
||||||
data.unshift({
|
|
||||||
label: '',
|
|
||||||
value: null,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(courses, () => {
|
|
||||||
if (courses.data) {
|
|
||||||
Object.keys(courses.data).forEach((section) => {
|
|
||||||
if (courses.data[section].length) {
|
|
||||||
hasCourses.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentCategory.value,
|
|
||||||
() => {
|
|
||||||
let queries = new URLSearchParams(location.search)
|
|
||||||
if (currentCategory.value) {
|
|
||||||
queries.set('category', currentCategory.value)
|
|
||||||
} else {
|
|
||||||
queries.delete('category')
|
|
||||||
}
|
|
||||||
history.pushState(null, '', `${location.pathname}?${queries.toString()}`)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const pageMeta = computed(() => {
|
const pageMeta = computed(() => {
|
||||||
return {
|
return {
|
||||||
title: 'Courses',
|
title: 'Courses',
|
||||||
description: 'All Courses divided by categories',
|
description: 'All published courses.',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
1.5: '1.5',
|
1.5: '1.5',
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
'2xl': '1536px',
|
'2xl': '1600px',
|
||||||
'3xl': '1920px',
|
'3xl': '1920px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -242,14 +242,14 @@
|
|||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "enrollments",
|
"fieldname": "enrollments",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Int",
|
||||||
"label": "Enrollments",
|
"label": "Enrollments",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "lessons",
|
"fieldname": "lessons",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Int",
|
||||||
"label": "Lessons",
|
"label": "Lessons",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2025-02-24 11:50:58.325804",
|
"modified": "2025-03-04 15:43:25.151554",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
|
|||||||
140
lms/lms/utils.py
140
lms/lms/utils.py
@@ -985,17 +985,145 @@ def change_currency(amount, currency, country=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_courses():
|
def get_courses(filters=None, start=0, page_length=20):
|
||||||
"""Returns the list of courses."""
|
"""Returns the list of courses."""
|
||||||
courses = []
|
|
||||||
course_list = frappe.get_all("LMS Course", pluck="name")
|
|
||||||
for course in course_list:
|
|
||||||
courses.append(get_course_details(course))
|
|
||||||
|
|
||||||
courses = get_categorized_courses(courses)
|
if not filters:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
filters, or_filters, show_featured = update_course_filters(filters)
|
||||||
|
fields = get_course_fields()
|
||||||
|
|
||||||
|
courses = frappe.get_all(
|
||||||
|
"LMS Course",
|
||||||
|
filters=filters,
|
||||||
|
fields=fields,
|
||||||
|
or_filters=or_filters,
|
||||||
|
order_by="enrollments desc",
|
||||||
|
start=start,
|
||||||
|
page_length=page_length,
|
||||||
|
)
|
||||||
|
if show_featured:
|
||||||
|
courses = get_featured_courses(filters, or_filters, fields) + courses
|
||||||
|
|
||||||
|
courses = get_enrollment_details(courses)
|
||||||
|
courses = get_course_card_details(courses)
|
||||||
return courses
|
return courses
|
||||||
|
|
||||||
|
|
||||||
|
def get_course_card_details(courses):
|
||||||
|
for course in courses:
|
||||||
|
course.instructors = get_instructors(course.name)
|
||||||
|
|
||||||
|
if course.paid_course and course.published == 1:
|
||||||
|
course.amount, course.currency = check_multicurrency(
|
||||||
|
course.course_price, course.currency, None, course.amount_usd
|
||||||
|
)
|
||||||
|
course.price = fmt_money(course.amount, 0, course.currency)
|
||||||
|
|
||||||
|
return courses
|
||||||
|
|
||||||
|
|
||||||
|
def get_course_or_filters(filters):
|
||||||
|
or_filters = {}
|
||||||
|
or_filters.update({"title": filters.get("title")})
|
||||||
|
or_filters.update({"short_introduction": filters.get("title")})
|
||||||
|
or_filters.update({"description": filters.get("title")})
|
||||||
|
or_filters.update({"tags": filters.get("title")})
|
||||||
|
return or_filters
|
||||||
|
|
||||||
|
|
||||||
|
def update_course_filters(filters):
|
||||||
|
or_filters = {}
|
||||||
|
show_featured = False
|
||||||
|
|
||||||
|
if filters.get("title"):
|
||||||
|
or_filters = get_course_or_filters(filters)
|
||||||
|
del filters["title"]
|
||||||
|
|
||||||
|
if filters.get("enrolled"):
|
||||||
|
enrolled_courses = frappe.get_all(
|
||||||
|
"LMS Enrollment", {"member": frappe.session.user}, pluck="course"
|
||||||
|
)
|
||||||
|
filters.update({"name": ["in", enrolled_courses]})
|
||||||
|
del filters["enrolled"]
|
||||||
|
|
||||||
|
if filters.get("created"):
|
||||||
|
created_courses = frappe.get_all(
|
||||||
|
"Course Instructor", {"instructor": frappe.session.user}, pluck="parent"
|
||||||
|
)
|
||||||
|
filters.update({"name": ["in", created_courses]})
|
||||||
|
del filters["created"]
|
||||||
|
|
||||||
|
if filters.get("live"):
|
||||||
|
filters.update({"featured": 0})
|
||||||
|
show_featured = True
|
||||||
|
del filters["live"]
|
||||||
|
|
||||||
|
if filters.get("certification"):
|
||||||
|
or_filters.update({"enable_certification": 1})
|
||||||
|
or_filters.update({"paid_certificate": 1})
|
||||||
|
del filters["certification"]
|
||||||
|
|
||||||
|
return filters, or_filters, show_featured
|
||||||
|
|
||||||
|
|
||||||
|
def get_enrollment_details(courses):
|
||||||
|
for course in courses:
|
||||||
|
filters = {
|
||||||
|
"course": course.name,
|
||||||
|
"member": frappe.session.user,
|
||||||
|
}
|
||||||
|
|
||||||
|
if frappe.db.exists("LMS Enrollment", filters):
|
||||||
|
course.membership = frappe.db.get_value(
|
||||||
|
"LMS Enrollment",
|
||||||
|
filters,
|
||||||
|
["name", "course", "current_lesson", "progress", "member"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return courses
|
||||||
|
|
||||||
|
|
||||||
|
def get_featured_courses(filters, or_filters, fields):
|
||||||
|
filters.update({"featured": 1})
|
||||||
|
featured_courses = frappe.get_all(
|
||||||
|
"LMS Course",
|
||||||
|
filters=filters,
|
||||||
|
fields=fields,
|
||||||
|
or_filters=or_filters,
|
||||||
|
order_by="enrollments desc",
|
||||||
|
)
|
||||||
|
return featured_courses
|
||||||
|
|
||||||
|
|
||||||
|
def get_course_fields():
|
||||||
|
return [
|
||||||
|
"name",
|
||||||
|
"title",
|
||||||
|
"tags",
|
||||||
|
"image",
|
||||||
|
"short_introduction",
|
||||||
|
"published",
|
||||||
|
"upcoming",
|
||||||
|
"featured",
|
||||||
|
"disable_self_learning",
|
||||||
|
"published_on",
|
||||||
|
"category",
|
||||||
|
"status",
|
||||||
|
"paid_course",
|
||||||
|
"paid_certificate",
|
||||||
|
"course_price",
|
||||||
|
"currency",
|
||||||
|
"amount_usd",
|
||||||
|
"enable_certification",
|
||||||
|
"lessons",
|
||||||
|
"enrollments",
|
||||||
|
"rating",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_course_details(course):
|
def get_course_details(course):
|
||||||
course_details = frappe.db.get_value(
|
course_details = frappe.db.get_value(
|
||||||
|
|||||||
Reference in New Issue
Block a user