mirror of
https://github.com/frappe/lms.git
synced 2026-05-02 13:39:31 +03:00
feat: home page
This commit is contained in:
@@ -365,7 +365,7 @@ const addPrograms = async () => {
|
||||
let canAddProgram = await checkIfCanAddProgram()
|
||||
if (!canAddProgram) return
|
||||
let activeFor = ['Programs', 'ProgramDetail']
|
||||
let index = 1
|
||||
let index = 2
|
||||
|
||||
sidebarLinks.value.splice(index, 0, {
|
||||
label: 'Programs',
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<Clock class="w-4 h-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ formatTime(cls.time) }} -
|
||||
{{ dayjs(getClassEnd(cls)).format('HH:mm') }}
|
||||
{{ dayjs(getClassEnd(cls)).format('HH:mm A') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
class="flex items-center w-full duration-300 ease-in-out group"
|
||||
:class="isCollapsed ? 'p-1 relative' : 'px-2 py-1'"
|
||||
>
|
||||
<Tooltip :text="link.label" placement="right">
|
||||
<Tooltip :text="__(link.label)" placement="right">
|
||||
<slot name="icon">
|
||||
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
||||
<component
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!forHome || (forHome && upcoming_evals.data?.length)">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Upcoming Evaluations') }}
|
||||
</div>
|
||||
<Button
|
||||
v-if="upcoming_evals.data?.length != evaluationCourses.length"
|
||||
v-if="
|
||||
upcoming_evals.data?.length != evaluationCourses.length && !forHome
|
||||
"
|
||||
@click="openEvalModal"
|
||||
>
|
||||
{{ __('Schedule Evaluation') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="upcoming_evals.data?.length">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="grid gap-4" :class="forHome ? 'grid-cols-2' : 'grid-cols-3'">
|
||||
<div v-for="evl in upcoming_evals.data">
|
||||
<div class="border text-ink-gray-7 rounded-md p-3">
|
||||
<div class="flex justify-between mb-3">
|
||||
<span class="font-semibold text-ink-gray-9 leading-5">
|
||||
<span class="text-lg font-semibold text-ink-gray-9 leading-5">
|
||||
{{ evl.course_title }}
|
||||
</span>
|
||||
<Menu
|
||||
@@ -94,8 +96,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-sm italic text-ink-gray-5">
|
||||
{{ __('Please schedule an evaluation to get certified.') }}
|
||||
<div v-else class="text-ink-gray-5">
|
||||
{{ __('Schedule an evaluation to get certified.') }}
|
||||
</div>
|
||||
</div>
|
||||
<EvaluationModal
|
||||
@@ -122,7 +124,6 @@ import EvaluationModal from '@/components/Modals/EvaluationModal.vue'
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
|
||||
const dayjs = inject('$dayjs')
|
||||
const user = inject('$user')
|
||||
const showEvalModal = ref(false)
|
||||
const app = getCurrentInstance()
|
||||
const { $dialog } = app.appContext.config.globalProperties
|
||||
@@ -140,12 +141,15 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
forHome: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const upcoming_evals = createResource({
|
||||
url: 'lms.lms.utils.get_upcoming_evals',
|
||||
params: {
|
||||
student: user.data.name,
|
||||
courses: props.courses.map((course) => course.course),
|
||||
batch: props.batch,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="createdCourses.data?.length" class="mt-10">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-semibold text-lg">
|
||||
{{ __('Courses by me') }}
|
||||
</span>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Courses',
|
||||
}"
|
||||
>
|
||||
<span class="flex items-center space-x-1 text-ink-gray-5 text-xs">
|
||||
<span>
|
||||
{{ __('See all') }}
|
||||
</span>
|
||||
<MoveRight class="size-3 stroke-1.5" />
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
<router-link
|
||||
v-for="course in createdCourses.data"
|
||||
:to="{ name: 'CourseDetail', params: { courseName: course.name } }"
|
||||
>
|
||||
<CourseCard :course="course" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="createdBatches.data?.length" class="mt-10">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-semibold text-lg">
|
||||
{{ __('Batches by me') }}
|
||||
</span>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Batches',
|
||||
}"
|
||||
>
|
||||
<span class="flex items-center space-x-1 text-ink-gray-5 text-xs">
|
||||
<span>
|
||||
{{ __('See all') }}
|
||||
</span>
|
||||
<MoveRight class="size-3 stroke-1.5" />
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||
<router-link
|
||||
v-for="batch in createdBatches.data"
|
||||
:to="{ name: 'BatchDetail', params: { batchName: batch.name } }"
|
||||
>
|
||||
<BatchCard :batch="batch" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { createResource } from 'frappe-ui'
|
||||
import { MoveRight } from 'lucide-vue-next'
|
||||
import CourseCard from '@/components/CourseCard.vue'
|
||||
import BatchCard from '@/components/BatchCard.vue'
|
||||
|
||||
const createdCourses = createResource({
|
||||
url: 'lms.lms.utils.get_created_courses',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const createdBatches = createResource({
|
||||
url: 'lms.lms.utils.get_created_batches',
|
||||
auto: true,
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<!-- <header
|
||||
class="sticky flex items-center justify-between top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
<Breadcrumbs :items="[{ label: __('Home'), route: { name: 'Home' } }]" />
|
||||
</header> -->
|
||||
<div class="w-full px-5 pt-10 pb-10">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="space-y-2">
|
||||
<div class="text-xl font-bold">
|
||||
{{ __('Hey') }}, {{ user.data?.full_name }} 👋
|
||||
</div>
|
||||
<div class="text-lg text-ink-gray-6">
|
||||
<span v-if="isAdmin">
|
||||
{{ __('Manage your courses and batches at a glance') }}
|
||||
</span>
|
||||
<span v-else-if="myLiveClasses.data?.length > 0 || evalCount > 0">
|
||||
<span v-if="myLiveClasses.data?.length > 0">
|
||||
{{
|
||||
__('You have {0} upcoming live classes').format(
|
||||
myLiveClasses.data.length
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span v-if="evalCount > 0">
|
||||
{{ __(' and {0} evaluation').format(evalCount) }}
|
||||
</span>
|
||||
<span>
|
||||
{{ __(' scheduled.') }}
|
||||
</span>
|
||||
</span>
|
||||
<span v-else-if="myLiveClasses.data?.length > 0">
|
||||
{{
|
||||
__('You have {0} upcoming live classes.').format(
|
||||
myLiveClasses.data.length
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span v-else-if="evalCount > 0">
|
||||
{{ __('You have {0} evaluations scheduled.').format(evalCount) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ __('Resume where you left off') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<TabButtons v-if="isAdmin" v-model="currentTab" :buttons="tabs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AdminHome v-if="isAdmin && currentTab === 'instructor'" />
|
||||
<StudentHome v-else :myLiveClasses="myLiveClasses" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, onMounted, ref } from 'vue'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
call,
|
||||
createResource,
|
||||
TabButtons,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import StudentHome from '@/pages/Home/StudentHome.vue'
|
||||
import AdminHome from '@/pages/Home/AdminHome.vue'
|
||||
|
||||
const user = inject<any>('$user')
|
||||
const { brand } = sessionStore()
|
||||
const evalCount = ref(0)
|
||||
const currentTab = ref<'student' | 'instructor'>('instructor')
|
||||
|
||||
onMounted(() => {
|
||||
call('lms.lms.utils.get_upcoming_evals').then((data: any) => {
|
||||
evalCount.value = data.length
|
||||
})
|
||||
})
|
||||
|
||||
const myLiveClasses = createResource({
|
||||
url: 'lms.lms.utils.get_my_live_classes',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const tabs = [
|
||||
{ label: __('Student'), value: 'student' },
|
||||
{ label: __('Instructor'), value: 'instructor' },
|
||||
]
|
||||
|
||||
const isAdmin = computed(() => {
|
||||
return (
|
||||
user.data?.is_moderator ||
|
||||
user.data?.is_instructor ||
|
||||
user.data?.is_evaluator
|
||||
)
|
||||
})
|
||||
|
||||
usePageMeta(() => {
|
||||
return {
|
||||
title: __('Home'),
|
||||
icon: brand.favicon,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="myCourses.data?.length" class="mt-10">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-semibold text-lg">
|
||||
{{ __('My Courses') }}
|
||||
</span>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Courses',
|
||||
}"
|
||||
>
|
||||
<span class="flex items-center space-x-1 text-ink-gray-5 text-xs">
|
||||
<span>
|
||||
{{ __('See all') }}
|
||||
</span>
|
||||
<MoveRight class="size-3 stroke-1.5" />
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
<router-link
|
||||
v-for="course in myCourses.data"
|
||||
:to="{ name: 'CourseDetail', params: { courseName: course.name } }"
|
||||
>
|
||||
<CourseCard :course="course" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="myBatches.data?.length" class="mt-10">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-semibold text-lg">
|
||||
{{ __('My Batches') }}
|
||||
</span>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Batches',
|
||||
}"
|
||||
>
|
||||
<span class="flex items-center space-x- 1 text-ink-gray-5 text-xs">
|
||||
<span>
|
||||
{{ __('See all') }}
|
||||
</span>
|
||||
<MoveRight class="size-3 stroke-1.5" />
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||
<router-link
|
||||
v-for="batch in myBatches.data"
|
||||
:to="{ name: 'BatchDetail', params: { batchName: batch.name } }"
|
||||
>
|
||||
<BatchCard :batch="batch" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-5 mt-10">
|
||||
<UpcomingEvaluations :forHome="true" />
|
||||
<div v-if="myLiveClasses.data?.length">
|
||||
<div class="font-semibold text-lg mb-3">
|
||||
{{ __('Upcoming Live Classes') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div v-for="cls in myLiveClasses.data" class="border rounded-md p-2">
|
||||
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
|
||||
{{ cls.title }}
|
||||
</div>
|
||||
<div class="text-ink-gray-7 text-sm leading-5 mb-4">
|
||||
{{ cls.description }}
|
||||
</div>
|
||||
<div class="mt-auto space-y-3 text-ink-gray-7 text-sm">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Calendar class="w-4 h-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ dayjs(cls.date).format('DD MMMM YYYY') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Clock class="w-4 h-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ formatTime(cls.time) }} -
|
||||
{{ dayjs(getClassEnd(cls)).format('HH:mm A') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="canAccessClass(cls)"
|
||||
class="flex items-center space-x-2 text-ink-gray-9 mt-auto"
|
||||
>
|
||||
<a
|
||||
v-if="user.data?.is_moderator || user.data?.is_evaluator"
|
||||
:href="cls.start_url"
|
||||
target="_blank"
|
||||
class="cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
|
||||
:class="cls.join_url ? 'w-full' : 'w-1/2'"
|
||||
>
|
||||
<Monitor class="h-4 w-4 stroke-1.5" />
|
||||
{{ __('Start') }}
|
||||
</a>
|
||||
<a
|
||||
:href="cls.join_url"
|
||||
target="_blank"
|
||||
class="w-full cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
|
||||
>
|
||||
<Video class="h-4 w-4 stroke-1.5" />
|
||||
{{ __('Join') }}
|
||||
</a>
|
||||
</div>
|
||||
<Tooltip
|
||||
v-else-if="hasClassEnded(cls)"
|
||||
:text="__('This class has ended')"
|
||||
placement="right"
|
||||
>
|
||||
<div class="flex items-center space-x-2 text-ink-amber-3 w-fit">
|
||||
<Info class="w-4 h-4 stroke-1.5" />
|
||||
<span>
|
||||
{{ __('Ended') }}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue'
|
||||
import { createResource, Tooltip } from 'frappe-ui'
|
||||
import { formatTime } from '@/utils'
|
||||
import {
|
||||
Calendar,
|
||||
Clock,
|
||||
Info,
|
||||
Monitor,
|
||||
MoveRight,
|
||||
Video,
|
||||
} from 'lucide-vue-next'
|
||||
import CourseCard from '@/components/CourseCard.vue'
|
||||
import BatchCard from '@/components/BatchCard.vue'
|
||||
import UpcomingEvaluations from '@/components/UpcomingEvaluations.vue'
|
||||
|
||||
const dayjs = inject<any>('$dayjs')
|
||||
const user = inject<any>('$user')
|
||||
|
||||
const props = defineProps<{
|
||||
myLiveClasses: any
|
||||
}>()
|
||||
|
||||
const myCourses = createResource({
|
||||
url: 'lms.lms.utils.get_my_courses',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const myBatches = createResource({
|
||||
url: 'lms.lms.utils.get_my_batches',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const streakInfo = createResource({
|
||||
url: 'lms.lms.utils.get_streak_info',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const getClassEnd = (cls: { date: string; time: string; duration: number }) => {
|
||||
const classStart = new Date(`${cls.date}T${cls.time}`)
|
||||
return new Date(classStart.getTime() + cls.duration * 60000)
|
||||
}
|
||||
|
||||
const canAccessClass = (cls: {
|
||||
date: string
|
||||
time: string
|
||||
duration: number
|
||||
}) => {
|
||||
if (cls.date < dayjs().format('YYYY-MM-DD')) return false
|
||||
if (cls.date > dayjs().format('YYYY-MM-DD')) return false
|
||||
if (hasClassEnded(cls)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const hasClassEnded = (cls: {
|
||||
date: string
|
||||
time: string
|
||||
duration: number
|
||||
}) => {
|
||||
const classEnd = getClassEnd(cls)
|
||||
const now = new Date()
|
||||
return now > classEnd
|
||||
}
|
||||
</script>
|
||||
@@ -3,13 +3,11 @@ import { usersStore } from './stores/user'
|
||||
import { sessionStore } from './stores/session'
|
||||
import { useSettings } from './stores/settings'
|
||||
|
||||
let defaultRoute = '/courses'
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: {
|
||||
name: 'Courses',
|
||||
},
|
||||
name: 'Home',
|
||||
component: () => import('@/pages/Home/Home.vue'),
|
||||
},
|
||||
{
|
||||
path: '/courses',
|
||||
|
||||
@@ -403,6 +403,12 @@ export function getUserTimezone() {
|
||||
|
||||
export function getSidebarLinks() {
|
||||
return [
|
||||
{
|
||||
label: 'Home',
|
||||
icon: 'Home',
|
||||
to: 'Home',
|
||||
activeFor: ['Home'],
|
||||
},
|
||||
{
|
||||
label: 'Courses',
|
||||
icon: 'BookOpen',
|
||||
|
||||
+232
-10
@@ -2,6 +2,7 @@ import hashlib
|
||||
import json
|
||||
import re
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import frappe
|
||||
import razorpay
|
||||
@@ -39,12 +40,12 @@ def slugify(title, used_slugs=None):
|
||||
If a list of used slugs is specified, it will make sure the generated slug
|
||||
is not one of them.
|
||||
|
||||
>>> slugify("Hello World!")
|
||||
'hello-world'
|
||||
>>> slugify("Hello World!", ["hello-world"])
|
||||
'hello-world-2'
|
||||
>>> slugify("Hello World!", ["hello-world", "hello-world-2"])
|
||||
'hello-world-3'
|
||||
>>> slugify("Hello World!")
|
||||
'hello-world'
|
||||
>>> slugify("Hello World!", ["hello-world"])
|
||||
'hello-world-2'
|
||||
>>> slugify("Hello World!", ["hello-world", "hello-world-2"])
|
||||
'hello-world-3'
|
||||
"""
|
||||
if not used_slugs:
|
||||
used_slugs = []
|
||||
@@ -844,14 +845,22 @@ def get_evaluator(course, batch=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_upcoming_evals(student, courses, batch=None):
|
||||
def get_upcoming_evals(courses=None, batch=None):
|
||||
if frappe.session.user == "Guest":
|
||||
return []
|
||||
|
||||
if not courses:
|
||||
courses = []
|
||||
|
||||
filters = {
|
||||
"member": student,
|
||||
"course": ["in", courses],
|
||||
"member": frappe.session.user,
|
||||
"date": [">=", frappe.utils.nowdate()],
|
||||
"status": "Upcoming",
|
||||
}
|
||||
|
||||
if len(courses) > 0:
|
||||
filters["course"] = ["in", courses]
|
||||
|
||||
if batch:
|
||||
filters["batch_name"] = batch
|
||||
|
||||
@@ -1127,7 +1136,7 @@ def get_course_details(course):
|
||||
# course_details.is_instructor = is_instructor(course_details.name)
|
||||
if course_details.paid_course or course_details.paid_certificate:
|
||||
"""course_details.course_price, course_details.currency = check_multicurrency(
|
||||
course_details.course_price, course_details.currency, None, course_details.amount_usd
|
||||
course_details.course_price, course_details.currency, None, course_details.amount_usd
|
||||
)"""
|
||||
course_details.price = fmt_money(course_details.course_price, 0, course_details.currency)
|
||||
|
||||
@@ -2133,3 +2142,216 @@ def get_related_courses(course):
|
||||
|
||||
def persona_captured():
|
||||
frappe.db.set_single_value("LMS Settings", "persona_captured", 1)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_my_courses():
|
||||
my_courses = []
|
||||
if frappe.session.user == "Guest":
|
||||
return my_courses
|
||||
|
||||
courses = frappe.get_all(
|
||||
"LMS Enrollment",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
order_by="creation desc",
|
||||
limit=3,
|
||||
pluck="course",
|
||||
)
|
||||
|
||||
for course in courses:
|
||||
my_courses.append(get_course_details(course))
|
||||
|
||||
return my_courses
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_my_batches():
|
||||
my_batches = []
|
||||
if frappe.session.user == "Guest":
|
||||
return my_batches
|
||||
|
||||
batches = frappe.get_all(
|
||||
"LMS Batch Enrollment",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
order_by="creation desc",
|
||||
limit=4,
|
||||
pluck="batch",
|
||||
)
|
||||
|
||||
for batch in batches:
|
||||
batch_details = get_batch_details(batch)
|
||||
if batch_details:
|
||||
my_batches.append(batch_details)
|
||||
|
||||
return my_batches
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_my_live_classes():
|
||||
my_live_classes = []
|
||||
if frappe.session.user == "Guest":
|
||||
return my_live_classes
|
||||
|
||||
batches = frappe.get_all(
|
||||
"LMS Batch Enrollment",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
order_by="creation desc",
|
||||
pluck="batch",
|
||||
)
|
||||
|
||||
live_class_details = frappe.get_all(
|
||||
"LMS Live Class",
|
||||
filters={
|
||||
"date": [">=", getdate()],
|
||||
"batch_name": ["in", batches],
|
||||
},
|
||||
fields=[
|
||||
"name",
|
||||
"title",
|
||||
"description",
|
||||
"time",
|
||||
"date",
|
||||
"duration",
|
||||
"attendees",
|
||||
"start_url",
|
||||
"join_url",
|
||||
"owner",
|
||||
],
|
||||
limit=2,
|
||||
order_by="date",
|
||||
)
|
||||
|
||||
if len(live_class_details):
|
||||
for live_class in live_class_details:
|
||||
live_class.course_title = frappe.db.get_value("LMS Course", live_class.course, "title")
|
||||
|
||||
my_live_classes.append(live_class)
|
||||
|
||||
return my_live_classes
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_created_courses():
|
||||
created_courses = []
|
||||
if frappe.session.user == "Guest":
|
||||
return created_courses
|
||||
|
||||
courses = frappe.get_all(
|
||||
"Course Instructor",
|
||||
{
|
||||
"instructor": frappe.session.user,
|
||||
"parenttype": "LMS Course",
|
||||
},
|
||||
pluck="parent",
|
||||
limit=3,
|
||||
order_by="creation desc",
|
||||
)
|
||||
|
||||
for course in courses:
|
||||
course_details = get_course_details(course)
|
||||
created_courses.append(course_details)
|
||||
|
||||
return created_courses
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_created_batches():
|
||||
created_batches = []
|
||||
if frappe.session.user == "Guest":
|
||||
return created_batches
|
||||
|
||||
batches = frappe.get_all(
|
||||
"Course Instructor",
|
||||
{"instructor": frappe.session.user, "parenttype": "LMS Batch"},
|
||||
pluck="parent",
|
||||
limit=4,
|
||||
order_by="creation asc",
|
||||
)
|
||||
|
||||
for batch in batches:
|
||||
batch_details = get_batch_details(batch)
|
||||
created_batches.append(batch_details)
|
||||
|
||||
return created_batches
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_streak_info():
|
||||
if frappe.session.user == "Guest":
|
||||
return {}
|
||||
|
||||
course_dates = frappe.get_all(
|
||||
"LMS Course Progress",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
pluck="creation",
|
||||
)
|
||||
|
||||
quiz_dates = frappe.get_all(
|
||||
"LMS Quiz Submission",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
pluck="creation",
|
||||
)
|
||||
|
||||
assignment_dates = frappe.get_all(
|
||||
"LMS Assignment Submission",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
pluck="creation",
|
||||
)
|
||||
|
||||
programming_exercise_dates = frappe.get_all(
|
||||
"LMS Programming Exercise Submission",
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
},
|
||||
pluck="creation",
|
||||
)
|
||||
|
||||
all_dates = set(course_dates + quiz_dates + assignment_dates + programming_exercise_dates)
|
||||
all_dates = sorted(all_dates)
|
||||
|
||||
streak = 0
|
||||
max_streak = 0
|
||||
prev_day = None
|
||||
|
||||
for d in all_dates:
|
||||
# skip weekends entirely
|
||||
print(d.weekday())
|
||||
if d.weekday() in (5, 6): # Saturday=5, Sunday=6
|
||||
continue
|
||||
|
||||
if prev_day:
|
||||
# if yesterday (excluding weekend gaps)
|
||||
expected = prev_day + timedelta(days=1)
|
||||
print(expected.weekday())
|
||||
while expected.weekday() in (5, 6): # skip Sat/Sun
|
||||
expected += timedelta(days=1)
|
||||
|
||||
if d == expected:
|
||||
streak += 1
|
||||
else:
|
||||
streak = 1
|
||||
else:
|
||||
streak = 1
|
||||
|
||||
max_streak = max(max_streak, streak)
|
||||
prev_day = d
|
||||
return 1
|
||||
""" return {
|
||||
"current_streak": streak,
|
||||
"max_streak": max_streak,
|
||||
"last_activity_date": prev_day.strftime("%Y-%m-%d") if prev_day else None,
|
||||
"total_days_active": len(all_dates),
|
||||
"total_days_in_month": (getdate() - getdate(getdate().year, getdate().month, 1)).days + 1,
|
||||
} """
|
||||
|
||||
Reference in New Issue
Block a user