355 lines
8.0 KiB
Vue
355 lines
8.0 KiB
Vue
<template>
|
|
<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"
|
|
>
|
|
<Breadcrumbs :items="breadcrumbs" />
|
|
<router-link
|
|
v-if="canCreateCourse()"
|
|
:to="{
|
|
name: 'CourseForm',
|
|
params: { courseName: 'new' },
|
|
}"
|
|
>
|
|
<Button variant="solid">
|
|
<template #prefix>
|
|
<Plus class="h-4 w-4 stroke-1.5" />
|
|
</template>
|
|
{{ __('New') }}
|
|
</Button>
|
|
</router-link>
|
|
</header>
|
|
<div class="p-5 pb-10">
|
|
<div
|
|
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">
|
|
{{ __('All Courses') }}
|
|
</div>
|
|
<div
|
|
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" />
|
|
<FormControl
|
|
v-model="certification"
|
|
:label="__('Certification')"
|
|
type="checkbox"
|
|
@change="updateCourses()"
|
|
/>
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<FormControl
|
|
v-model="title"
|
|
:placeholder="__('Search by Title')"
|
|
type="text"
|
|
class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40"
|
|
@input="updateCourses()"
|
|
/>
|
|
<div class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40">
|
|
<Select
|
|
v-if="categories.length"
|
|
v-model="currentCategory"
|
|
:options="categories"
|
|
:placeholder="__('Category')"
|
|
@change="updateCourses()"
|
|
/>
|
|
</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>
|
|
</template>
|
|
<script setup>
|
|
import {
|
|
Breadcrumbs,
|
|
Button,
|
|
call,
|
|
createListResource,
|
|
FormControl,
|
|
Select,
|
|
TabButtons,
|
|
usePageMeta,
|
|
} from 'frappe-ui'
|
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
|
import { BookOpen, Plus } from 'lucide-vue-next'
|
|
import { sessionStore } from '@/stores/session'
|
|
import { canCreateCourse } from '@/utils'
|
|
import CourseCard from '@/components/CourseCard.vue'
|
|
import router from '../router'
|
|
|
|
const user = inject('$user')
|
|
const dayjs = inject('$dayjs')
|
|
const start = ref(0)
|
|
const pageLength = ref(30)
|
|
const categories = ref([])
|
|
const currentCategory = ref(null)
|
|
const title = ref('')
|
|
const certification = ref(false)
|
|
const filters = ref({})
|
|
const currentTab = ref('Live')
|
|
const { brand } = sessionStore()
|
|
|
|
onMounted(() => {
|
|
identifyUserPersona()
|
|
setFiltersFromQuery()
|
|
updateCourses()
|
|
categories.value = [
|
|
{
|
|
label: '',
|
|
value: null,
|
|
},
|
|
]
|
|
})
|
|
|
|
const setFiltersFromQuery = () => {
|
|
let queries = new URLSearchParams(location.search)
|
|
title.value = queries.get('title') || ''
|
|
currentCategory.value = queries.get('category') || null
|
|
certification.value = queries.get('certification') || false
|
|
}
|
|
|
|
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) {
|
|
setCategories(data)
|
|
},
|
|
})
|
|
|
|
const setCategories = (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 isPersonaCaptured = async () => {
|
|
let persona = await call('frappe.client.get_single_value', {
|
|
doctype: 'LMS Settings',
|
|
field: 'persona_captured',
|
|
})
|
|
return persona
|
|
}
|
|
|
|
const identifyUserPersona = async () => {
|
|
let personaCaptured = await isPersonaCaptured()
|
|
debugger
|
|
console.log('personaCaptured', personaCaptured)
|
|
console.log('user.data?.is_system_manager', user.data?.is_system_manager)
|
|
console.log('user.data?.developer_mode', user.data?.developer_mode)
|
|
|
|
if (
|
|
user.data?.is_system_manager &&
|
|
!user.data?.developer_mode &&
|
|
!personaCaptured
|
|
) {
|
|
call('frappe.client.get_count', {
|
|
doctype: 'LMS Course',
|
|
}).then((data) => {
|
|
if (!data) {
|
|
router.push({
|
|
name: 'PersonaForm',
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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 updateTitleFilter = () => {
|
|
if (title.value) {
|
|
filters.value['title'] = ['like', `%${title.value}%`]
|
|
} else {
|
|
delete filters.value['title']
|
|
}
|
|
}
|
|
|
|
const updateCertificationFilter = () => {
|
|
if (certification.value) {
|
|
filters.value['certification'] = 1
|
|
} else {
|
|
delete filters.value['certification']
|
|
}
|
|
}
|
|
|
|
const updateTabFilter = () => {
|
|
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)
|
|
}
|
|
})
|
|
|
|
let queryString = ''
|
|
if (queries.toString()) {
|
|
queryString = `?${queries.toString()}`
|
|
}
|
|
|
|
history.replaceState({}, '', `${location.pathname}${queryString}`)
|
|
}
|
|
|
|
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 courseTabs = computed(() => {
|
|
let tabs = [
|
|
{
|
|
label: __('Live'),
|
|
},
|
|
{
|
|
label: __('New'),
|
|
},
|
|
{
|
|
label: __('Upcoming'),
|
|
},
|
|
]
|
|
if (
|
|
user.data?.is_moderator ||
|
|
user.data?.is_instructor ||
|
|
user.data?.is_evaluator
|
|
) {
|
|
tabs.push({ label: __('Created') })
|
|
} else if (user.data) {
|
|
tabs.push({ label: __('Enrolled') })
|
|
}
|
|
return tabs
|
|
})
|
|
|
|
const breadcrumbs = computed(() => [
|
|
{
|
|
label: __('Courses'),
|
|
route: { name: 'Courses' },
|
|
},
|
|
])
|
|
|
|
usePageMeta(() => {
|
|
return {
|
|
title: __('Courses'),
|
|
icon: brand.favicon,
|
|
}
|
|
})
|
|
</script>
|