feat: certification details and form
This commit is contained in:
@@ -108,6 +108,7 @@ const options = createResource({
|
||||
url: 'frappe.desk.search.search_link',
|
||||
cache: [props.doctype, text.value],
|
||||
method: 'POST',
|
||||
auto: true,
|
||||
params: {
|
||||
txt: text.value,
|
||||
doctype: props.doctype,
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
{{ props.label }}
|
||||
</label>
|
||||
<div class="flex text-center">
|
||||
<div v-for="index in 5">
|
||||
{{ rating }}
|
||||
<div v-for="index in 5" @mouseover="hoveredRating = index" @mouseleave="hoveredRating = 0">
|
||||
<Star
|
||||
:class="index <= rating ? 'fill-orange-500' : ''"
|
||||
class="h-6 w-6 fill-gray-400 text-gray-50 mr-1 cursor-pointer"
|
||||
class="h-6 w-6 fill-gray-400 text-gray-50 stroke-1 mr-1 cursor-pointer"
|
||||
:class="{ 'fill-yellow-200': (index <= hoveredRating && index > rating), 'fill-yellow-500': index <= rating }"
|
||||
@click="markRating(index)"
|
||||
/>
|
||||
</div>
|
||||
@@ -18,7 +17,7 @@
|
||||
|
||||
<script setup>
|
||||
import { Star } from 'lucide-vue-next'
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -36,8 +35,9 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
let rating = ref(props.modelValue)
|
||||
console.log(props.modelValue)
|
||||
const rating = ref(props.modelValue)
|
||||
const hoveredRating = ref(0)
|
||||
|
||||
let emitChange = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
@@ -46,4 +46,8 @@ function markRating(index) {
|
||||
emitChange(index)
|
||||
rating.value = index
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
rating.value = newVal
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -11,76 +11,118 @@
|
||||
{{ event.title }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-3">
|
||||
<div class="flex items-center space-x-1">
|
||||
<BookOpen class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ event.course_title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<Calendar class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ dayjs(event.date).format("DD MMM YYYY") }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<Clock class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ formatTime(event.start_time) }} - {{ formatTime(event.end_time) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<User class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ event.member }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4 text-sm text-gray-800">
|
||||
<Tooltip :text="__('Email ID')">
|
||||
<div class="flex items-center space-x-2 w-fit">
|
||||
<User class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ event.member }}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip :text="__('Course')">
|
||||
<div class="flex items-center space-x-2 w-fit">
|
||||
<BookOpen class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ event.course_title }}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="event.batch_title" :text="__('Batch')">
|
||||
<div class="flex items-center space-x-2 w-fit">
|
||||
<Users class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ event.batch_title }}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip :text="__('Date')">
|
||||
<div class="flex items-center space-x-2 w-fit">
|
||||
<Calendar class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ dayjs(event.date).format("DD MMM YYYY") }}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip :text="__('Time')">
|
||||
<div class="flex items-center space-x-2 w-fit">
|
||||
<Clock class="h-4 w-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ formatTime(event.start_time) }} - {{ formatTime(event.end_time) }}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 mt-auto">
|
||||
<Button v-if="certificate.name" @click="openCertificate(certificate)" class="w-full">
|
||||
<template #prefix>
|
||||
<FileText class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
{{ __("View Certificate") }}
|
||||
</Button>
|
||||
<Button v-else @click="openCallLink(event.venue)" class="w-full">
|
||||
<template #prefix>
|
||||
<Video class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
<span>
|
||||
{{ __("Join Meeting") }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Button @click="openCallLink(event.venue)" class="mt-auto">
|
||||
<template #prefix>
|
||||
<Video class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
<span>
|
||||
{{ __("Join Meeting") }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4 border-l w-1/2 p-5">
|
||||
{{ evaluation.rating }}
|
||||
<Rating v-model="evaluation.rating" :label="__('Rating')"/>
|
||||
<FormControl type="select" :options='[{
|
||||
value: "Pending",
|
||||
label: __("Pending")
|
||||
}, {
|
||||
value: "In Progress",
|
||||
label: __("In Progress")
|
||||
}, {
|
||||
value: "Pass",
|
||||
label: __("Pass")
|
||||
}, {
|
||||
value: "Fail",
|
||||
label: __("Fail")
|
||||
}]'
|
||||
v-model="evaluation.status" :label="__('Status')" />
|
||||
<FormControl type="textarea" v-model="evaluation.summary" :label="__('Summary')" />
|
||||
<Button variant="solid" @click="saveEvaluation()">
|
||||
{{ __("Save") }}
|
||||
</Button>
|
||||
</div>
|
||||
<Tabs :tabs='tabs' v-model="tabIndex" class="border-l w-1/2">
|
||||
<template #default="{ tab }">
|
||||
<div v-if="tab.label == 'Evaluation'" class="flex flex-col space-y-4 p-5">
|
||||
<Rating v-model="evaluation.rating" :label="__('Rating')"/>
|
||||
<FormControl type="select" :options='[{
|
||||
value: "Pending",
|
||||
label: __("Pending")
|
||||
}, {
|
||||
value: "In Progress",
|
||||
label: __("In Progress")
|
||||
}, {
|
||||
value: "Pass",
|
||||
label: __("Pass")
|
||||
}, {
|
||||
value: "Fail",
|
||||
label: __("Fail")
|
||||
}]'
|
||||
v-model="evaluation.status" :label="__('Status')" />
|
||||
<FormControl type="textarea" v-model="evaluation.summary" :label="__('Summary')" />
|
||||
<Button variant="solid" @click="saveEvaluation()">
|
||||
{{ __("Save") }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="flex flex-col space-y-4 p-5">
|
||||
|
||||
<FormControl type="checkbox" v-model="certificate.published" :label="__('Published')" />
|
||||
<Link v-model="certificate.template" :label="__('Template')" doctype="Print Format" :filters='{
|
||||
"doc_type": "LMS Certificate"
|
||||
}' />
|
||||
<FormControl type="date" v-model="certificate.issue_date" :label="__('Issue Date')" />
|
||||
<FormControl type="date" v-model="certificate.expiry_date" :label="__('Expiry Date')" />
|
||||
<Button variant="solid" @click="saveCertificate()">
|
||||
{{ __("Save") }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Dialog, Button, FormControl, createResource } from 'frappe-ui';
|
||||
import { User, Calendar, Clock, Video, BookOpen } from "lucide-vue-next"
|
||||
import { inject, reactive, watch } from "vue"
|
||||
import { Dialog, Button, FormControl, createResource, Tabs, Tooltip } from 'frappe-ui';
|
||||
import { User, Calendar, Clock, Video, BookOpen, FileText, GraduationCap, Users, ClipboardList } from "lucide-vue-next"
|
||||
import { inject, reactive, watch, ref, computed } from "vue"
|
||||
import { formatTime, showToast } from "@/utils"
|
||||
import Rating from "@/components/Controls/Rating.vue"
|
||||
import Link from "@/components/Controls/Link.vue"
|
||||
|
||||
const show = defineModel()
|
||||
const dayjs = inject("$dayjs")
|
||||
const tabIndex = ref(0)
|
||||
const showCertification = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
event: {
|
||||
@@ -89,10 +131,26 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const evaluation = reactive({
|
||||
rating: 0,
|
||||
status: "Pending",
|
||||
summary: "",
|
||||
const evaluation = reactive({})
|
||||
|
||||
const certificate = reactive({})
|
||||
|
||||
const defaultTemplate = createResource({
|
||||
url: "frappe.client.get_value",
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: "Property Setter",
|
||||
fieldname: "value",
|
||||
filters: {
|
||||
doc_type: "LMS Certificate",
|
||||
property: "default_print_format"
|
||||
}
|
||||
}
|
||||
},
|
||||
auto: true,
|
||||
onSuccess(data) {
|
||||
certificate.template = data.value
|
||||
}
|
||||
})
|
||||
|
||||
const openCallLink = (link) => {
|
||||
@@ -105,6 +163,7 @@ const evaluationResource = createResource({
|
||||
return {
|
||||
member: props.event.member,
|
||||
course: props.event.course,
|
||||
batch_name: props.event.batch_name,
|
||||
date: props.event.date,
|
||||
start_time: props.event.start_time,
|
||||
end_time: props.event.end_time,
|
||||
@@ -113,7 +172,10 @@ const evaluationResource = createResource({
|
||||
summary: evaluation.summary,
|
||||
}
|
||||
},
|
||||
auto: false
|
||||
auto: false,
|
||||
onSuccess(data) {
|
||||
evaluation.name = data.name
|
||||
}
|
||||
})
|
||||
|
||||
const evaluationDetails = createResource({
|
||||
@@ -131,6 +193,10 @@ const evaluationDetails = createResource({
|
||||
for (const key in data) {
|
||||
if (key in evaluation)
|
||||
evaluation[key] = data[key]
|
||||
if (key == "rating")
|
||||
evaluation.rating = data.rating * 5
|
||||
if (evaluation.status == "Pass")
|
||||
showCertification.value = true
|
||||
}
|
||||
},
|
||||
auto: false
|
||||
@@ -139,18 +205,105 @@ const evaluationDetails = createResource({
|
||||
const saveEvaluation = () => {
|
||||
evaluationResource.submit({}, {
|
||||
onSuccess: () => {
|
||||
show.value = false
|
||||
if (evaluation.status == "Pass") {
|
||||
showCertification.value = true
|
||||
} else {
|
||||
show.value = false
|
||||
}
|
||||
showToast( __("Success"), __("Evaluation saved successfully"), "check")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const certificateResource = createResource({
|
||||
url: "lms.lms.api.save_certificate_details",
|
||||
makeParams(values) {
|
||||
return {
|
||||
member: props.event.member,
|
||||
course: props.event.course,
|
||||
batch_name: props.event.batch_name,
|
||||
published: certificate.published,
|
||||
issue_date: certificate.issue_date,
|
||||
expiry_date: certificate.expiry_date,
|
||||
template: certificate.template,
|
||||
}
|
||||
},
|
||||
auto: false,
|
||||
onSuccess(data) {
|
||||
certificate.name = data
|
||||
}
|
||||
})
|
||||
|
||||
const certificateDetails = createResource({
|
||||
url: "frappe.client.get",
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: "LMS Certificate",
|
||||
filters: {
|
||||
member: props.event.member,
|
||||
course: props.event.course,
|
||||
}
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
for (const key in data) {
|
||||
if (key in certificate)
|
||||
certificate[key] = data[key]
|
||||
certificate.name = data.name
|
||||
showCertification.value = true
|
||||
}
|
||||
},
|
||||
onError(err) {
|
||||
certificate.template = defaultTemplate.data.value
|
||||
},
|
||||
auto: false
|
||||
})
|
||||
|
||||
const saveCertificate = () => {
|
||||
certificateResource.submit({}, {
|
||||
onSuccess: () => {
|
||||
showToast( __("Success"), __("Certificate saved successfully"), "check")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(show, () => {
|
||||
if (show.value) {
|
||||
evaluation.rating = 0
|
||||
evaluation.status = "Pending"
|
||||
evaluation.summary = ""
|
||||
evaluationDetails.reload()
|
||||
|
||||
certificate.published = true
|
||||
certificate.issue_date = dayjs().format("YYYY-MM-DD")
|
||||
certificate.expiry_date = null
|
||||
certificate.template = null
|
||||
certificate.name = null
|
||||
certificateDetails.reload()
|
||||
}
|
||||
})
|
||||
|
||||
const openCertificate = (certificate) => {
|
||||
window.open(
|
||||
`/api/method/frappe.utils.print_format.download_pdf?doctype=LMS+Certificate&name=${certificate.name}&format=${encodeURIComponent(certificate.template)}`
|
||||
)
|
||||
}
|
||||
|
||||
const tabs = computed(() => {
|
||||
const tabsArray = [
|
||||
{
|
||||
label: __("Evaluation"),
|
||||
icon: ClipboardList,
|
||||
},
|
||||
]
|
||||
|
||||
if (showCertification.value) {
|
||||
tabsArray.push({
|
||||
label: __("Certificate"),
|
||||
icon: GraduationCap,
|
||||
})
|
||||
}
|
||||
|
||||
return tabsArray
|
||||
})
|
||||
</script>
|
||||
@@ -7,7 +7,7 @@
|
||||
defaultMode: 'Month',
|
||||
disableModes: ['Day', 'Week'],
|
||||
redundantCellHeight: 100,
|
||||
enableShortcuts: true,
|
||||
enableShortcuts: false,
|
||||
}"
|
||||
:events="evaluations.data"
|
||||
@click="(event) => openEvent(event)"
|
||||
@@ -62,7 +62,7 @@ const evaluations = createListResource({
|
||||
filters: {
|
||||
"evaluator": user.data?.name
|
||||
},
|
||||
fields: ["name", "member_name", "member", "course", "course_title", "date", "start_time", "end_time", "google_meet_link"],
|
||||
fields: ["name", "member_name", "member", "course", "course_title", "batch_name", "batch_title", "date", "start_time", "end_time", "google_meet_link"],
|
||||
auto: true,
|
||||
cache: ["schedule", user.data?.name],
|
||||
transform(data) {
|
||||
@@ -80,6 +80,8 @@ const evaluations = createListResource({
|
||||
course: d.course,
|
||||
course_title: d.course_title,
|
||||
member: d.member,
|
||||
batch_name: d.batch_name,
|
||||
batch_title: d.batch_title
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user