Merge pull request #1251 from pateljannat/issues-67

fix: misc issues
This commit is contained in:
Jannat Patel
2025-01-16 13:07:23 +05:30
committed by GitHub
8 changed files with 116 additions and 59 deletions

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="{{ favicon or '/assets/lms/frontend/favicon.png' }}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Frappe Learning</title> <title>Frappe Learning</title>
<meta name="title" content="{{ meta.title }}" /> <meta name="title" content="{{ meta.title }}" />

View File

@@ -17,12 +17,6 @@
> >
<template #body-content> <template #body-content>
<div class="space-y-4"> <div class="space-y-4">
<FormControl
type="select"
v-model="details.course"
:label="__('Course')"
:options="getCourses()"
/>
<Link <Link
v-model="details.evaluator" v-model="details.evaluator"
:label="__('Evaluator')" :label="__('Evaluator')"
@@ -38,6 +32,12 @@
v-model="details.expiry_date" v-model="details.expiry_date"
:label="__('Expiry Date')" :label="__('Expiry Date')"
/> />
<FormControl
type="select"
v-model="details.course"
:label="__('Course')"
:options="getCourses()"
/>
<Link <Link
v-model="details.template" v-model="details.template"
:label="__('Template')" :label="__('Template')"
@@ -94,7 +94,7 @@ const createCertificate = createResource({
template: details.template, template: details.template,
published: details.published, published: details.published,
course: values.course, course: values.course,
batch: values.batch, batch_name: values.batch,
member: values.member, member: values.member,
evaluator: details.evaluator, evaluator: details.evaluator,
}, },

View File

@@ -38,10 +38,10 @@
v-model="title" v-model="title"
:placeholder="__('Search by Title')" :placeholder="__('Search by Title')"
type="text" type="text"
class="min-w-40 lg:min-w-0 lg:w-32" class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40"
@input="updateBatches()" @input="updateBatches()"
/> />
<div class="min-w-40 lg:min-w-0 lg:w-32"> <div class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40">
<Select <Select
v-if="categories.length" v-if="categories.length"
v-model="currentCategory" v-model="currentCategory"
@@ -101,6 +101,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { computed, inject, onMounted, ref, watch } from 'vue' import { computed, inject, onMounted, ref, watch } from 'vue'
import { BookOpen, Plus } from 'lucide-vue-next' import { BookOpen, Plus } from 'lucide-vue-next'
import { updateDocumentTitle } from '@/utils'
import BatchCard from '@/components/BatchCard.vue' import BatchCard from '@/components/BatchCard.vue'
const user = inject('$user') const user = inject('$user')
@@ -188,6 +189,8 @@ const updateTabFilter = () => {
} }
if (currentTab.value == 'Enrolled' && user.data?.is_student) { if (currentTab.value == 'Enrolled' && user.data?.is_student) {
filters.value['enrolled'] = 1 filters.value['enrolled'] = 1
delete filters.value['start_date']
delete filters.value['published']
orderBy.value = 'start_date desc' orderBy.value = 'start_date desc'
} else if (user.data?.is_student) { } else if (user.data?.is_student) {
delete filters.value['enrolled'] delete filters.value['enrolled']
@@ -200,7 +203,7 @@ const updateTabFilter = () => {
filters.value['published'] = 1 filters.value['published'] = 1
orderBy.value = 'start_date' orderBy.value = 'start_date'
} else if (currentTab.value == 'Archived') { } else if (currentTab.value == 'Archived') {
filters.value['start_date'] = ['<', dayjs().format('YYYY-MM-DD')] filters.value['start_date'] = ['<=', dayjs().format('YYYY-MM-DD')]
} else if (currentTab.value == 'Unpublished') { } else if (currentTab.value == 'Unpublished') {
filters.value['published'] = 0 filters.value['published'] = 0
} }
@@ -208,7 +211,7 @@ const updateTabFilter = () => {
} }
const updateStudentFilter = () => { const updateStudentFilter = () => {
if (!user.data || user.data?.is_student) { if (!user.data || (user.data?.is_student && currentTab.value != 'Enrolled')) {
filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')] filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')]
filters.value['published'] = 1 filters.value['published'] = 1
} }
@@ -283,4 +286,13 @@ const breadcrumbs = computed(() => [
route: { name: 'Batches' }, route: { name: 'Batches' },
}, },
]) ])
const pageMeta = computed(() => {
return {
title: 'Batches',
description: 'All upcoming batches.',
}
})
updateDocumentTitle(pageMeta)
</script> </script>

View File

@@ -7,14 +7,14 @@
<div <div
v-for="certificate in certificates.data" v-for="certificate in certificates.data"
:key="certificate.name" :key="certificate.name"
class="bg-white shadow rounded-lg p-3 cursor-pointer" class="flex flex-col bg-white shadow rounded-lg p-3 cursor-pointer hover:bg-gray-50"
@click="openCertificate(certificate)" @click="openCertificate(certificate)"
> >
<div class="font-medium leading-5"> <div class="font-medium leading-5 mb-2">
{{ certificate.course_title }} {{ certificate.course_title || certificate.batch_title }}
</div> </div>
<div class="mt-2"> <div class="text-sm text-gray-700 font-medium mt-auto">
<span class="text-xs text-gray-700"> {{ __('issued on') }}: </span> <span> {{ __('Issued on') }}: </span>
{{ dayjs(certificate.issue_date).format('DD MMM YYYY') }} {{ dayjs(certificate.issue_date).format('DD MMM YYYY') }}
</div> </div>
</div> </div>
@@ -22,8 +22,8 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { createResource } from 'frappe-ui' import { createListResource } from 'frappe-ui'
import { inject } from 'vue' import { inject, onMounted } from 'vue'
const dayjs = inject('$dayjs') const dayjs = inject('$dayjs')
const props = defineProps({ const props = defineProps({
@@ -33,12 +33,19 @@ const props = defineProps({
}, },
}) })
const certificates = createResource({ onMounted(() => {
url: 'lms.lms.api.get_certificates', if (props.profile.data?.name) {
params: { certificates.reload()
member: props.profile.data.name, }
})
const certificates = createListResource({
doctype: 'LMS Certificate',
filters: {
member: props.profile.data?.name,
}, },
auto: true, fields: ['name', 'course_title', 'batch_title', 'issue_date'],
cache: ['certificates', props.profile.data?.name],
}) })
const openCertificate = (certificate) => { const openCertificate = (certificate) => {

View File

@@ -407,17 +407,6 @@ def get_assigned_badges(member):
return assigned_badges return assigned_badges
@frappe.whitelist()
def get_certificates(member):
"""Get certificates for a member."""
return frappe.get_all(
"LMS Certificate",
filters={"member": member},
fields=["name", "course", "course_title", "issue_date", "template"],
order_by="creation desc",
)
@frappe.whitelist() @frappe.whitelist()
def get_all_users(): def get_all_users():
users = frappe.get_all( users = frappe.get_all(

View File

@@ -1,25 +1,27 @@
{ {
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "hash",
"creation": "2021-08-16 15:47:19.494055", "creation": "2021-08-16 15:47:19.494055",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"course",
"course_title",
"member", "member",
"member_name", "member_name",
"column_break_vwbn",
"issue_date",
"template",
"published",
"section_break_scyf",
"evaluator", "evaluator",
"evaluator_name", "evaluator_name",
"column_break_slaw", "column_break_vwbn",
"issue_date",
"expiry_date", "expiry_date",
"batch_name" "template",
"published",
"section_break_unwn",
"course",
"course_title",
"column_break_ywee",
"batch_name",
"batch_title"
], ],
"fields": [ "fields": [
{ {
@@ -32,11 +34,9 @@
{ {
"fieldname": "course", "fieldname": "course",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Course", "label": "Course",
"options": "LMS Course", "options": "LMS Course"
"reqd": 1
}, },
{ {
"fieldname": "expiry_date", "fieldname": "expiry_date",
@@ -46,7 +46,6 @@
{ {
"fieldname": "member", "fieldname": "member",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Member", "label": "Member",
"options": "User", "options": "User",
@@ -56,6 +55,8 @@
"fetch_from": "member.full_name", "fetch_from": "member.full_name",
"fieldname": "member_name", "fieldname": "member_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Member Name", "label": "Member Name",
"read_only": 1 "read_only": 1
}, },
@@ -90,14 +91,6 @@
"fieldname": "column_break_vwbn", "fieldname": "column_break_vwbn",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{
"fieldname": "section_break_scyf",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_slaw",
"fieldtype": "Column Break"
},
{ {
"fieldname": "evaluator", "fieldname": "evaluator",
"fieldtype": "Link", "fieldtype": "Link",
@@ -108,16 +101,33 @@
"fetch_from": "evaluator.full_name", "fetch_from": "evaluator.full_name",
"fieldname": "evaluator_name", "fieldname": "evaluator_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1,
"label": "Evaluator Name", "label": "Evaluator Name",
"read_only": 1 "read_only": 1
},
{
"fieldname": "section_break_unwn",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_ywee",
"fieldtype": "Column Break"
},
{
"fetch_from": "batch_name.title",
"fieldname": "batch_title",
"fieldtype": "Data",
"label": "Batch Title",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2024-09-11 11:37:20.419956", "modified": "2025-01-16 12:12:49.998114",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Certificate", "name": "LMS Certificate",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -1861,8 +1861,6 @@ def get_batches(filters=None, start=0, page_length=20, order_by="start_date"):
) )
filters.update({"name": ["in", enrolled_batches]}) filters.update({"name": ["in", enrolled_batches]})
del filters["enrolled"] del filters["enrolled"]
del filters["published"]
del filters["start_date"]
batches = frappe.get_all( batches = frappe.get_all(
"LMS Batch", "LMS Batch",
@@ -1889,6 +1887,46 @@ def get_batches(filters=None, start=0, page_length=20, order_by="start_date"):
page_length=page_length, page_length=page_length,
) )
batches = filter_batches_based_on_start_time(batches, filters)
batches = get_batch_card_details(batches)
return batches
def filter_batches_based_on_start_time(batches, filters):
batchType = get_batch_type(filters)
if batchType == "upcoming":
batches_to_remove = [
batch
for batch in batches
if getdate(batch.start_date) == getdate()
and get_time_str(batch.start_time) < nowtime()
]
batches = [batch for batch in batches if batch not in batches_to_remove]
elif batchType == "archived":
batches_to_remove = [
batch
for batch in batches
if getdate(batch.start_date) == getdate()
and get_time_str(batch.start_time) >= nowtime()
]
batches = [batch for batch in batches if batch not in batches_to_remove]
return batches
def get_batch_type(filters):
start_date_filter = filters.get("start_date")
batchType = None
if start_date_filter:
sign = start_date_filter[0]
if ">" in sign:
batchType = "upcoming"
elif "<" in sign:
batchType = "archived"
return batchType
def get_batch_card_details(batches):
for batch in batches: for batch in batches:
batch.instructors = get_instructors(batch.name) batch.instructors = get_instructors(batch.name)
students_count = frappe.db.count("Batch Student", {"parent": batch.name}) students_count = frappe.db.count("Batch Student", {"parent": batch.name})

View File

@@ -18,6 +18,7 @@ def get_context():
frappe.db.commit() # nosemgrep frappe.db.commit() # nosemgrep
context.csrf_token = csrf_token context.csrf_token = csrf_token
capture("active_site", "lms") capture("active_site", "lms")
context.favicon = frappe.db.get_single_value("Website Settings", "favicon")
return context return context