fix: list pagination

This commit is contained in:
Jannat Patel
2026-04-10 19:54:59 +05:30
parent c7ccb2d1c5
commit af08e6842a
6 changed files with 102 additions and 54 deletions

View File

@@ -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>

View File

@@ -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') }}

View File

@@ -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' },
]
})

View File

@@ -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>

View File

@@ -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',
},

View File

@@ -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)