mirror of
https://github.com/frappe/lms.git
synced 2026-04-19 22:52:29 +03:00
refactor: certified participants list
This commit is contained in:
@@ -182,6 +182,7 @@ watch([titleFilter, typeFilter], () => {
|
||||
totalAssignments.update({
|
||||
filters: assignmentFilter.value,
|
||||
})
|
||||
totalAssignments.reload()
|
||||
})
|
||||
|
||||
const reloadAssignments = () => {
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
</Button>
|
||||
</router-link>
|
||||
</header>
|
||||
<div class="mx-auto w-full max-w-4xl pt-6 pb-10">
|
||||
<div class="flex flex-col md:flex-row justify-between mb-8 px-3">
|
||||
<div class="text-xl font-semibold text-ink-gray-9 mb-4 md:mb-0">
|
||||
<div class="mx-auto w-full">
|
||||
<div class="flex flex-col md:flex-row justify-between mb-5 px-5 pt-5">
|
||||
<div class="text-lg font-semibold text-ink-gray-9 mb-4 md:mb-0">
|
||||
{{ memberCount }} {{ __('Certified Members') }}
|
||||
</div>
|
||||
<div
|
||||
@@ -56,67 +56,67 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="participants.data?.length" class="">
|
||||
<template v-for="(participant, index) in participants.data">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'ProfileAbout',
|
||||
params: {
|
||||
username: participant.username,
|
||||
},
|
||||
}"
|
||||
<div
|
||||
v-if="participants.data?.length"
|
||||
class="h-[76vh] overflow-y-auto mb-5 px-5"
|
||||
>
|
||||
<div class="grid grid-cols-4 gap-5">
|
||||
<div
|
||||
v-for="participant in participants.data"
|
||||
class="flex flex-col border hover:border-outline-gray-3 rounded-lg p-3 text-ink-gray-9 cursor-pointer"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'ProfileAbout',
|
||||
params: { username: participant.username },
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="rounded-md hover:bg-surface-gray-2 px-3">
|
||||
<div
|
||||
class="flex items-center w-full space-x-3 py-2"
|
||||
:class="{
|
||||
'border-b': index < participants.data.length - 1,
|
||||
}"
|
||||
>
|
||||
<UserAvatar :user="participant" size="2xl" />
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full">
|
||||
<div class="flex-1">
|
||||
<div class="text-base font-medium text-ink-gray-8">
|
||||
{{ participant.full_name }}
|
||||
</div>
|
||||
<div
|
||||
v-if="participant.headline"
|
||||
class="mt-1.5 text-base text-ink-gray-5"
|
||||
>
|
||||
{{ participant.headline }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-3 md:space-x-24 text-sm md:text-base mt-1.5"
|
||||
>
|
||||
<div class="text-ink-gray-5">
|
||||
{{ participant.certificate_count }}
|
||||
{{
|
||||
participant.certificate_count > 1
|
||||
? __('certificates')
|
||||
: __('certificate')
|
||||
}}
|
||||
</div>
|
||||
<span class="text-ink-gray-4 md:hidden">·</span>
|
||||
<div class="text-ink-gray-5">
|
||||
{{ dayjs(participant.issue_date).format('DD MMM YYYY') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-4">
|
||||
<UserAvatar :user="participant" size="2xl" />
|
||||
<div class="flex flex-col">
|
||||
<div class="font-semibold mb-1">
|
||||
{{ participant.full_name }}
|
||||
</div>
|
||||
<div class="text-sm leading-5 line-clamp-1 mb-4">
|
||||
{{
|
||||
participant.headline ||
|
||||
'Joined ' + dayjs(participant.creation).fromNow()
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
<div class="mt-auto space-y-2 text-ink-gray-7">
|
||||
<div class="flex items-center space-x-1">
|
||||
<GraduationCap class="h-4 w-4 stroke-1.5 mr-1" />
|
||||
<span>
|
||||
{{ participant.certificate_count }}
|
||||
{{
|
||||
participant.certificate_count > 1
|
||||
? __('certificates')
|
||||
: __('certificate')
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<Calendar class="h-4 w-4 stroke-1.5 mr-1" />
|
||||
<span>{{
|
||||
dayjs(participant.issue_date).format('DD MMM YYYY')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState v-else type="Certified Members" />
|
||||
<div
|
||||
v-if="!participants.list.loading && participants.hasNextPage"
|
||||
class="flex justify-center mt-5"
|
||||
>
|
||||
<Button @click="participants.next()">
|
||||
<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') }}
|
||||
</Button>
|
||||
<div v-if="participants.hasNextPage" class="h-8 border-l"></div>
|
||||
<div class="text-ink-gray-5">
|
||||
{{ participants.data?.length }} {{ __('of') }}
|
||||
{{ memberCount }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -132,7 +132,7 @@ import {
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { computed, inject, onMounted, ref } from 'vue'
|
||||
import { GraduationCap } from 'lucide-vue-next'
|
||||
import { GraduationCap, Calendar } from 'lucide-vue-next'
|
||||
import { sessionStore } from '../stores/session'
|
||||
import { useRouter } from 'vue-router'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
@@ -163,7 +163,6 @@ const participants = createListResource({
|
||||
url: 'lms.lms.api.get_certified_participants',
|
||||
start: 0,
|
||||
cache: ['certified_participants'],
|
||||
pageLength: 100,
|
||||
})
|
||||
|
||||
const getMemberCount = () => {
|
||||
|
||||
@@ -109,93 +109,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="border-b mb-4 pb-5 px-5">
|
||||
<div class="text-lg font-semibold mb-4 text-ink-gray-9">
|
||||
{{ __('Job Details') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
<FormControl
|
||||
v-model="job.job_title"
|
||||
:label="__('Title')"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="job.type"
|
||||
:label="__('Type')"
|
||||
type="select"
|
||||
:options="jobTypes"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="job.work_mode"
|
||||
:label="__('Work Mode')"
|
||||
type="select"
|
||||
:options="workModes"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="job.location"
|
||||
:label="__('City')"
|
||||
:required="true"
|
||||
/>
|
||||
<Link
|
||||
v-model="job.country"
|
||||
doctype="Country"
|
||||
:label="__('Country')"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="jobName != 'new'"
|
||||
v-model="job.status"
|
||||
:label="__('Status')"
|
||||
type="select"
|
||||
:options="jobStatuses"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-b mb-4 pb-5 px-5">
|
||||
<div class="text-lg font-semibold mb-4 text-ink-gray-9">
|
||||
{{ __('Company Details') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
<FormControl
|
||||
v-model="job.company_name"
|
||||
:label="__('Company Name')"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="job.company_website"
|
||||
:label="__('Company Website')"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="job.company_email_address"
|
||||
:label="__('Company Email Address')"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<Uploader
|
||||
v-model="job.company_logo"
|
||||
:label="__('Company Logo')"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 mt-4">
|
||||
<label class="block text-ink-gray-5 text-xs mb-1">
|
||||
{{ __('Description') }}
|
||||
<span class="text-ink-red-3">*</span>
|
||||
</label>
|
||||
<TextEditor
|
||||
:content="job.description"
|
||||
@change="(val) => (job.description = val)"
|
||||
:editable="true"
|
||||
:fixedMenu="true"
|
||||
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[20rem] overflow-y-auto mb-4"
|
||||
/>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -203,6 +203,7 @@ const updateList = () => {
|
||||
totalExercises.update({
|
||||
filters: filters,
|
||||
})
|
||||
totalExercises.reload()
|
||||
}
|
||||
|
||||
const getFilters = () => {
|
||||
|
||||
@@ -344,11 +344,11 @@ def get_evaluator_details(evaluator: str):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_certified_participants(filters: dict = None, start: int = 0, page_length: int = 100):
|
||||
def get_certified_participants(filters: dict = None, start: int = 0, page_length: int = 40):
|
||||
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)
|
||||
@@ -361,7 +361,7 @@ def get_certified_participant_details(member: str):
|
||||
details = frappe.db.get_value(
|
||||
"User",
|
||||
member,
|
||||
["full_name", "user_image", "username", "country", "headline", "open_to"],
|
||||
["full_name", "user_image", "username", "creation", "headline", "open_to"],
|
||||
as_dict=1,
|
||||
)
|
||||
details["certificate_count"] = count
|
||||
@@ -374,12 +374,12 @@ def get_certification_query(filters: dict = None):
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(Certificate)
|
||||
.select(Certificate.member, Certificate.issue_date)
|
||||
.distinct()
|
||||
.select(Certificate.member, fn.Max(Certificate.issue_date).as_("issue_date"))
|
||||
.join(User)
|
||||
.on(Certificate.member == User.name)
|
||||
.where(Certificate.published == 1)
|
||||
.where(User.enabled == 1)
|
||||
.groupby(Certificate.member)
|
||||
)
|
||||
|
||||
if filters:
|
||||
|
||||
Reference in New Issue
Block a user