mirror of
https://github.com/frappe/lms.git
synced 2026-04-19 22:52:29 +03:00
fix: list pagination
This commit is contained in:
@@ -20,8 +20,8 @@
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<div class="py-5 mx-5">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div class="py-5">
|
||||
<div class="flex items-center justify-between mb-5 mx-5">
|
||||
<div class="text-lg font-semibold text-ink-gray-9">
|
||||
{{ __('{0} Assignments').format(assignments.data?.length) }}
|
||||
</div>
|
||||
@@ -52,7 +52,7 @@
|
||||
showAssignmentForm = true
|
||||
},
|
||||
}"
|
||||
class="h-[79vh] border-b"
|
||||
class="h-[79vh] px-5"
|
||||
>
|
||||
<ListHeader
|
||||
class="mb-2 grid items-center rounded bg-surface-white border-b rounded-none p-2"
|
||||
@@ -104,8 +104,10 @@
|
||||
</template>
|
||||
</ListSelectBanner>
|
||||
</ListView>
|
||||
<EmptyState v-else type="Assignments" />
|
||||
<div class="flex items-center justify-end space-x-3 mt-3">
|
||||
<div v-else class="h-[53vh]">
|
||||
<EmptyState type="Assignments" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-3 pt-3 border-t px-5">
|
||||
<Button v-if="assignments.hasNextPage" @click="assignments.next()">
|
||||
{{ __('Load More') }}
|
||||
</Button>
|
||||
|
||||
@@ -107,7 +107,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState v-else type="Certified Members" />
|
||||
<div v-else class="h-[53vh]">
|
||||
<EmptyState type="Certified Members" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-3 border-t pt-3 px-5">
|
||||
<Button v-if="participants.hasNextPage" @click="participants.next()">
|
||||
{{ __('Load More') }}
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between w-full mx-auto mb-2 p-5"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xl font-semibold text-ink-gray-9 md:mb-0">
|
||||
{{ __('{0} {1} Jobs').format(jobCount, activeTab) }}
|
||||
<div class="text-lg font-semibold text-ink-gray-9 md:mb-0">
|
||||
{{ __('{0} {1} Jobs').format(jobCount.data, activeTab) }}
|
||||
</div>
|
||||
<TabButtons
|
||||
v-if="tabs.length > 1"
|
||||
@@ -96,7 +96,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="jobs.data?.length" class="w-full mx-auto p-5 pt-0">
|
||||
<div
|
||||
v-if="jobs.data?.length"
|
||||
class="w-full h-[78vh] overflow-y-auto mx-auto p-5 pt-0"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<router-link
|
||||
v-for="job in jobs.data"
|
||||
@@ -110,7 +113,19 @@
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState v-else type="Job Openings" />
|
||||
<div v-else class="h-[50vh]">
|
||||
<EmptyState type="Job Openings" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-3 border-t pt-3 px-5">
|
||||
<Button v-if="jobs.hasNextPage" @click="jobs.next()">
|
||||
{{ __('Load More') }}
|
||||
</Button>
|
||||
<div v-if="jobs.hasNextPage" class="h-8 border-l"></div>
|
||||
<div class="text-ink-gray-5">
|
||||
{{ jobs.data?.length }} {{ __('of') }}
|
||||
{{ jobCount.data }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,6 +134,7 @@ import {
|
||||
Button,
|
||||
Breadcrumbs,
|
||||
call,
|
||||
createListResource,
|
||||
createResource,
|
||||
FormControl,
|
||||
TabButtons,
|
||||
@@ -141,7 +157,6 @@ const searchQuery = ref('')
|
||||
const country = ref(null)
|
||||
const filters = ref({})
|
||||
const orFilters = ref({})
|
||||
const jobCount = ref(0)
|
||||
const closedJobs = ref(0)
|
||||
const activeTab = ref('Open')
|
||||
const readOnlyMode = window.read_only_mode
|
||||
@@ -157,9 +172,7 @@ const isModerator = computed(() => {
|
||||
})
|
||||
|
||||
const getClosedJobCount = () => {
|
||||
if (!user.data?.name) {
|
||||
return
|
||||
}
|
||||
if (!user.data?.name) return
|
||||
|
||||
const filters = {
|
||||
status: 'Closed',
|
||||
@@ -177,6 +190,14 @@ const getClosedJobCount = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const jobCount = createResource({
|
||||
url: 'frappe.client.get_count',
|
||||
params: {
|
||||
doctype: 'Job Opportunity',
|
||||
filters: filters.value,
|
||||
},
|
||||
})
|
||||
|
||||
const setFiltersFromURL = () => {
|
||||
let queries = new URLSearchParams(location.search)
|
||||
if (queries.has('type')) {
|
||||
@@ -187,53 +208,53 @@ const setFiltersFromURL = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = computed(() => {
|
||||
const tabsArray = [
|
||||
{
|
||||
label: __('Open'),
|
||||
},
|
||||
]
|
||||
|
||||
if (closedJobs.value) {
|
||||
tabsArray.push({
|
||||
label: __('Closed'),
|
||||
})
|
||||
}
|
||||
|
||||
return tabsArray
|
||||
})
|
||||
|
||||
const jobs = createResource({
|
||||
const jobs = createListResource({
|
||||
url: 'lms.lms.api.get_job_opportunities',
|
||||
doctype: 'Job Opportunity',
|
||||
start: 0,
|
||||
cache: ['jobs'],
|
||||
})
|
||||
|
||||
const updateJobs = () => {
|
||||
updateFilters()
|
||||
jobs.update({
|
||||
params: {
|
||||
filters: filters.value,
|
||||
orFilters: orFilters.value,
|
||||
},
|
||||
filters: filters.value,
|
||||
orFilters: orFilters.value,
|
||||
})
|
||||
jobs.reload()
|
||||
jobCount.update({
|
||||
filters: filters.value,
|
||||
orFilters: orFilters.value,
|
||||
})
|
||||
jobCount.reload()
|
||||
}
|
||||
|
||||
const updateFilters = () => {
|
||||
filters.value.status = 'Open'
|
||||
updateJobTypeFilter()
|
||||
updateWorkModeFilter()
|
||||
updateSearchQueryFilter()
|
||||
updateCountryFilter()
|
||||
updateTabFilter()
|
||||
}
|
||||
|
||||
const updateJobTypeFilter = () => {
|
||||
if (jobType.value && jobType.value !== ' ') {
|
||||
filters.value.type = jobType.value
|
||||
} else {
|
||||
delete filters.value.type
|
||||
}
|
||||
}
|
||||
|
||||
const updateWorkModeFilter = () => {
|
||||
if (workMode.value && workMode.value !== ' ') {
|
||||
filters.value.work_mode = workMode.value
|
||||
} else {
|
||||
delete filters.value.work_mode
|
||||
}
|
||||
}
|
||||
|
||||
const updateSearchQueryFilter = () => {
|
||||
if (searchQuery.value) {
|
||||
orFilters.value = {
|
||||
job_title: ['like', `%${searchQuery.value}%`],
|
||||
@@ -243,13 +264,17 @@ const updateFilters = () => {
|
||||
} else {
|
||||
orFilters.value = {}
|
||||
}
|
||||
}
|
||||
|
||||
const updateCountryFilter = () => {
|
||||
if (country.value) {
|
||||
filters.value.country = country.value
|
||||
} else {
|
||||
delete filters.value.country
|
||||
}
|
||||
}
|
||||
|
||||
const updateTabFilter = () => {
|
||||
if (activeTab.value === 'Closed') {
|
||||
filters.value.status = 'Closed'
|
||||
if (!isModerator.value) {
|
||||
@@ -269,8 +294,20 @@ watch(country, (val) => {
|
||||
updateJobs()
|
||||
})
|
||||
|
||||
watch(jobs, () => {
|
||||
jobCount.value = jobs.data?.length || 0
|
||||
const tabs = computed(() => {
|
||||
const tabsArray = [
|
||||
{
|
||||
label: __('Open'),
|
||||
},
|
||||
]
|
||||
|
||||
if (closedJobs.value) {
|
||||
tabsArray.push({
|
||||
label: __('Closed'),
|
||||
})
|
||||
}
|
||||
|
||||
return tabsArray
|
||||
})
|
||||
|
||||
const jobTypes = computed(() => {
|
||||
@@ -286,9 +323,9 @@ const jobTypes = computed(() => {
|
||||
const workModes = computed(() => {
|
||||
return [
|
||||
{ label: ' ', value: ' ' },
|
||||
{ label: 'On site', value: 'On-site' },
|
||||
{ label: 'Hybrid', value: 'Hybrid' },
|
||||
{ label: 'Remote', value: 'Remote' },
|
||||
{ label: __('On-site'), value: 'On-site' },
|
||||
{ label: __('Hybrid'), value: 'Hybrid' },
|
||||
{ label: __('Remote'), value: 'Remote' },
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="p-5">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div class="py-5">
|
||||
<div class="flex items-center justify-between mb-5 px-5">
|
||||
<div class="text-lg font-semibold text-ink-gray-9">
|
||||
{{ __('{0} Exercises').format(exercises.data?.length) }}
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
showForm = true
|
||||
},
|
||||
}"
|
||||
class="h-[79vh] border-b"
|
||||
class="h-[79vh] px-5"
|
||||
>
|
||||
<ListHeader
|
||||
class="mb-2 grid items-center rounded bg-surface-white border-b rounded-none p-2"
|
||||
@@ -115,8 +115,10 @@
|
||||
</ListSelectBanner>
|
||||
</ListView>
|
||||
</div>
|
||||
<EmptyState v-else type="Programming Exercises" />
|
||||
<div class="flex items-center justify-end space-x-3 mt-3">
|
||||
<div v-else class="h-[53vh]">
|
||||
<EmptyState type="Programming Exercises" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-3 px-5 pt-3 border-t">
|
||||
<Button v-if="exercises.hasNextPage" @click="exercises.next()">
|
||||
{{ __('Load More') }}
|
||||
</Button>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</header>
|
||||
<div class="pt-5 mx-5">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div class="pt-5">
|
||||
<div class="flex items-center justify-between mb-5 mx-5">
|
||||
<div class="text-lg font-semibold text-ink-gray-9">
|
||||
{{ __('{0} Quizzes').format(quizzes.data?.length) }}
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@
|
||||
:rows="quizzes.data"
|
||||
row-key="name"
|
||||
:options="{ showTooltip: false, selectable: true }"
|
||||
class="h-[79vh] border-b"
|
||||
class="h-[79vh] px-5"
|
||||
>
|
||||
<ListHeader
|
||||
class="mb-2 grid items-center rounded bg-surface-white border-b rounded-none p-2"
|
||||
@@ -85,8 +85,10 @@
|
||||
</template>
|
||||
</ListSelectBanner>
|
||||
</ListView>
|
||||
<EmptyState v-else type="Quizzes" />
|
||||
<div class="flex items-center justify-end space-x-3 mt-3">
|
||||
<div v-else class="h-[53vh]">
|
||||
<EmptyState type="Quizzes" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-3 pt-3 border-t px-5">
|
||||
<Button v-if="quizzes.hasNextPage" @click="quizzes.next()">
|
||||
{{ __('Load More') }}
|
||||
</Button>
|
||||
@@ -296,7 +298,7 @@ const quizColumns = computed(() => {
|
||||
{
|
||||
label: __('Show Answers'),
|
||||
key: 'show_answers',
|
||||
width: 1,
|
||||
width: 0.5,
|
||||
align: 'center',
|
||||
icon: 'eye',
|
||||
},
|
||||
|
||||
@@ -232,14 +232,16 @@ def get_job_details(job: str):
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_job_opportunities(filters: dict = None, orFilters: dict = None):
|
||||
def get_job_opportunities(
|
||||
filters: dict = None, or_filters: dict = None, start: int = 0, page_length: int = 40
|
||||
):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
jobs = frappe.get_all(
|
||||
"Job Opportunity",
|
||||
filters=filters,
|
||||
or_filters=orFilters,
|
||||
or_filters=or_filters,
|
||||
fields=[
|
||||
"job_title",
|
||||
"location",
|
||||
@@ -252,6 +254,8 @@ def get_job_opportunities(filters: dict = None, orFilters: dict = None):
|
||||
"creation",
|
||||
"description",
|
||||
],
|
||||
start=start,
|
||||
page_length=page_length,
|
||||
order_by="creation desc",
|
||||
)
|
||||
|
||||
@@ -348,7 +352,6 @@ def get_certified_participants(filters: dict = None, start: int = 0, page_length
|
||||
query = get_certification_query(filters)
|
||||
query = query.orderby("issue_date", order=frappe.qb.desc).offset(start).limit(page_length)
|
||||
participants = query.run(as_dict=True)
|
||||
print(participants)
|
||||
for participant in participants:
|
||||
details = get_certified_participant_details(participant.member)
|
||||
participant.update(details)
|
||||
|
||||
Reference in New Issue
Block a user