refactor: improved performance and ui batch list

This commit is contained in:
Jannat Patel
2025-01-14 17:41:46 +05:30
parent 9a395cbda0
commit b42c635cdb
2 changed files with 286 additions and 220 deletions

View File

@@ -1,21 +1,8 @@
<template> <template>
<div class="">
<header <header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5" class="sticky flex items-center justify-between top-0 z-10 border-b bg-white px-3 py-2.5 sm:px-5"
> >
<Breadcrumbs <Breadcrumbs :items="breadcrumbs" />
class="h-7"
:items="[{ label: __('Batches'), route: { name: 'Batches' } }]"
/>
<div class="flex space-x-2">
<div class="w-44">
<Select
v-if="categories.data?.length"
v-model="currentCategory"
:options="categories.data"
:placeholder="__('Category')"
/>
</div>
<router-link <router-link
v-if="user.data?.is_moderator" v-if="user.data?.is_moderator"
:to="{ :to="{
@@ -30,227 +17,251 @@
{{ __('New') }} {{ __('New') }}
</Button> </Button>
</router-link> </router-link>
</div>
</header> </header>
<div v-if="batches.data" class="pb-5"> <div class="p-5 pb-10">
<div <div class="flex items-center justify-between mb-5">
v-if="batches.data.length == 0 && batches.list.loading" <div class="text-lg font-semibold">
class="p-5 text-base text-gray-700" {{ __('All Batches') }}
>
{{ __('Loading Batches...') }}
</div> </div>
<Tabs <div class="flex items-center space-x-2">
v-if="hasBatches" <TabButtons
v-model="tabIndex" v-if="user.data && user.data?.is_student"
:tabs="makeTabs" :buttons="batchTabs"
tablistClass="overflow-x-visible flex-wrap !gap-3 md:flex-nowrap" v-model="currentTab"
> />
<template #tab="{ tab, selected }"> <FormControl
<div> v-model="title"
<button :placeholder="__('Search by Title')"
class="group -mb-px flex items-center gap-2 border-b border-transparent py-2.5 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900" type="text"
:class="{ 'text-gray-900': selected }" @input="updateBatches()"
> />
<component v-if="tab.icon" :is="tab.icon" class="h-5" /> <div v-if="user.data && !user.data?.is_student" class="w-44">
{{ __(tab.label) }} <Select
<Badge v-model="currentDuration"
:class=" :options="batchType"
selected :placeholder="__('Type')"
? 'text-gray-800 border border-gray-800' @change="updateBatches()"
: 'border border-gray-500' />
"
variant="subtle"
theme="gray"
size="sm"
>
{{ tab.count }}
</Badge>
</button>
</div> </div>
</template> <div class="w-44">
<template #default="{ tab }"> <Select
<div v-if="categories.length"
v-if="tab.batches && tab.batches.value.length" v-model="currentCategory"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 m-5" :options="categories"
> :placeholder="__('Category')"
@change="updateBatches()"
/>
</div>
</div>
</div>
<div v-if="batches.data?.length" class="grid grid-cols-4 gap-5">
<router-link <router-link
v-for="batch in tab.batches.value" v-for="batch in batches.data"
:to="{ name: 'BatchDetail', params: { batchName: batch.name } }" :to="{ name: 'BatchDetail', params: { batchName: batch.name } }"
> >
<BatchCard :batch="batch" /> <BatchCard :batch="batch" />
</router-link> </router-link>
</div> </div>
<div v-else class="p-5 italic text-gray-500">
{{ __('No {0} batches').format(tab.label.toLowerCase()) }}
</div>
</template>
</Tabs>
<div <div
v-else-if=" v-else
!batches.loading && class="flex flex-col items-center justify-center text-sm text-gray-600 italic mt-48"
!hasBatches &&
(user.data?.is_instructor || user.data?.is_moderator)
"
class="grid grid-cols-3 p-5"
> >
<router-link <BookOpen class="size-10 mx-auto stroke-1.5 text-gray-500" />
:to="{ <div class="text-xl font-medium mb-2">
name: 'BatchForm',
params: {
batchName: 'new',
},
}"
>
<div class="bg-gray-50 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-gray-800 p-1 rounded-full border bg-white"
/>
<div class="font-medium">
{{ __('Create a Batch') }}
</div>
<span class="text-gray-700 text-sm leading-4">
{{ __('You can link courses and assessments to it.') }}
</span>
</div>
</div>
</router-link>
</div>
<div
v-else-if="!batches.loading && !hasBatches"
class="text-center p-5 text-gray-600 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
>
<BookOpen class="size-10 mx-auto stroke-1 text-gray-500" />
<div class="text-xl font-medium">
{{ __('No batches found') }} {{ __('No batches found') }}
</div> </div>
<div> <div class="leading-5 w-2/5 text-center">
{{ {{
__( __(
'There are no batches available at the moment. Keep an eye out, fresh learning experiences are on the way soon!' 'There are no batches matching the criteria. Keep an eye out, fresh learning experiences are on the way soon!'
) )
}} }}
</div> </div>
</div> </div>
<div
v-if="!batches.loading && batches.hasNextPage"
class="flex justify-center mt-5"
>
<Button @click="batches.next()">
{{ __('Load More') }}
</Button>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { import {
createResource,
Breadcrumbs, Breadcrumbs,
Button, Button,
Tabs, createListResource,
Badge, FormControl,
Select, Select,
TabButtons,
} from 'frappe-ui' } from 'frappe-ui'
import { computed, inject, onMounted, ref, watch } from 'vue'
import { BookOpen, Plus } from 'lucide-vue-next' import { BookOpen, Plus } from 'lucide-vue-next'
import BatchCard from '@/components/BatchCard.vue' import BatchCard from '@/components/BatchCard.vue'
import { inject, ref, computed, onMounted, watch } from 'vue'
import { updateDocumentTitle } from '@/utils'
const user = inject('$user') const user = inject('$user')
const dayjs = inject('$dayjs')
const start = ref(0)
const pageLength = ref(20)
const categories = ref([])
const currentCategory = ref(null) const currentCategory = ref(null)
const hasBatches = ref(false) const title = ref('')
const filters = ref({})
const currentDuration = ref(null)
const currentTab = ref('All')
onMounted(() => { onMounted(() => {
let queries = new URLSearchParams(location.search) setFiltersFromQuery()
if (queries.has('category')) { updateBatches()
currentCategory.value = queries.get('category') categories.value = [
} {
})
const batches = createResource({
doctype: 'LMS Batch',
url: 'lms.lms.utils.get_batches',
cache: ['batches', user.data?.email || ''],
auto: true,
})
const categories = createResource({
url: 'lms.lms.api.get_categories',
makeParams() {
return {
doctype: 'LMS Batch',
filters: {
published: 1,
},
}
},
cache: ['batchCategories'],
auto: true,
transform(data) {
data.unshift({
label: '', label: '',
value: null, value: null,
},
]
}) })
const setFiltersFromQuery = () => {
let queries = new URLSearchParams(location.search)
title.value = queries.get('title') || ''
currentCategory.value = queries.get('category') || null
currentDuration.value = queries.get('type') || null
}
const batches = createListResource({
doctype: 'LMS Batch',
url: 'lms.lms.utils.get_batches',
cache: ['batches', user.data?.name],
pageLength: pageLength.value,
start: start.value,
onSuccess(data) {
let allCategories = data.map((batch) => batch.category)
allCategories = allCategories.filter(
(category, index) => allCategories.indexOf(category) === index && category
)
if (categories.value.length <= allCategories.length) {
updateCategories(data)
}
}, },
}) })
const tabIndex = ref(0) const updateBatches = () => {
let tabs updateFilters()
batches.update({
filters: filters.value,
})
batches.reload()
}
const makeTabs = computed(() => { const updateFilters = () => {
tabs = [] if (currentCategory.value) {
addToTabs('Upcoming') filters.value['category'] = currentCategory.value
} else {
delete filters.value['category']
}
if (title.value) {
filters.value['title'] = ['like', `%${title.value}%`]
} else {
delete filters.value['title']
}
if (currentDuration.value) {
delete filters.value['start_date']
delete filters.value['published']
if (currentDuration.value == 'Upcoming') {
filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')]
} else if (currentDuration.value == 'Archived') {
filters.value['start_date'] = ['<', dayjs().format('YYYY-MM-DD')]
} else if (currentDuration.value == 'Unpublished') {
filters.value['published'] = 0
}
} else {
delete filters.value['start_date']
delete filters.value['published']
}
if (currentTab.value == 'Enrolled' && user.data?.is_student) {
filters.value['enrolled'] = 1
} else {
delete filters.value['enrolled']
}
if (!user.data || user.data?.is_student) {
filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')]
filters.value['published'] = 1
}
setQueryParams()
}
const setQueryParams = () => {
let queries = new URLSearchParams(location.search)
let filterKeys = {
title: title.value,
category: currentCategory.value,
type: currentDuration.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((batch) => {
if (
batch.category &&
!categories.value.find((category) => category.value === batch.category)
)
categories.value.push({
label: batch.category,
value: batch.category,
})
})
}
watch(currentTab, () => {
updateBatches()
})
const batchType = computed(() => {
let types = [
{ label: __(''), value: null },
{ label: __('Upcoming'), value: 'Upcoming' },
{ label: __('Archived'), value: 'Archived' },
]
if (user.data?.is_moderator) { if (user.data?.is_moderator) {
addToTabs('Archived') types.push({ label: __('Unpublished'), value: 'Unpublished' })
addToTabs('Private')
}
if (user.data) {
addToTabs('Enrolled')
} }
return types
})
const batchTabs = computed(() => {
let tabs = [
{
label: __('All'),
},
{
label: __('Enrolled'),
},
]
return tabs return tabs
}) })
const getBatches = (type) => { const breadcrumbs = computed(() => [
if (currentCategory.value && currentCategory.value != '') { {
return batches.data[type].filter( label: __('Batches'),
(batch) => batch.category == currentCategory.value route: { name: 'Batches' },
) },
} ])
return batches.data[type]
}
const addToTabs = (label) => {
let batches = getBatches(label.toLowerCase().split(' ').join('_'))
tabs.push({
label,
batches: computed(() => batches),
count: computed(() => batches.length),
})
}
watch(batches, () => {
Object.keys(batches.data).forEach((key) => {
if (batches.data[key].length) {
hasBatches.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(() => {
return {
title: 'Batches',
description: 'All batches divided by categories',
}
})
updateDocumentTitle(pageMeta)
</script> </script>

View File

@@ -1211,7 +1211,7 @@ def get_neighbour_lesson(course, chapter, lesson):
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def get_batches(): def get_batches1():
batches = [] batches = []
filters = {} filters = {}
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
@@ -1902,3 +1902,58 @@ def enroll_in_program_course(program, course):
) )
enrollment.save() enrollment.save()
return enrollment return enrollment
@frappe.whitelist(allow_guest=True)
def get_batches(filters=None, start=0, page_length=20):
if not filters:
filters = {}
if filters.get("enrolled"):
enrolled_batches = frappe.get_all(
"Batch Student", {"student": frappe.session.user}, pluck="parent"
)
filters.update({"name": ["in", enrolled_batches]})
del filters["enrolled"]
del filters["published"]
del filters["start_date"]
batches = frappe.get_all(
"LMS Batch",
filters=filters,
fields=[
"name",
"title",
"description",
"seat_count",
"paid_batch",
"amount",
"amount_usd",
"currency",
"start_date",
"end_date",
"start_time",
"end_time",
"timezone",
"published",
"category",
],
order_by="start_date desc",
start=start,
page_length=page_length,
)
for batch in batches:
batch.instructors = get_instructors(batch.name)
students_count = frappe.db.count("Batch Student", {"parent": batch.name})
if batch.seat_count:
batch.seats_left = batch.seat_count - students_count
if batch.paid_batch and batch.start_date >= getdate():
batch.amount, batch.currency = check_multicurrency(
batch.amount, batch.currency, None, batch.amount_usd
)
batch.price = fmt_money(batch.amount, 0, batch.currency)
return batches