fix: show video statistics watch time in minutes

This commit is contained in:
Jannat Patel
2025-07-30 11:30:17 +05:30
parent 26f9fb4199
commit d7e83bb78e
7 changed files with 77 additions and 30 deletions

View File

@@ -344,6 +344,22 @@ const addAssignments = () => {
} }
} }
const addProgrammingExercises = () => {
if (isInstructor.value || isModerator.value) {
sidebarLinks.value.splice(3, 0, {
label: 'Programming Exercises',
icon: 'Code',
to: 'ProgrammingExercises',
activeFor: [
'ProgrammingExercises',
'ProgrammingExerciseForm',
'ProgrammingExerciseSubmissions',
'ProgrammingExerciseSubmission',
],
})
}
}
const addPrograms = () => { const addPrograms = () => {
let activeFor = ['Programs', 'ProgramForm'] let activeFor = ['Programs', 'ProgramForm']
let index = 1 let index = 1
@@ -627,6 +643,7 @@ watch(userResource, () => {
isModerator.value = userResource.data.is_moderator isModerator.value = userResource.data.is_moderator
isInstructor.value = userResource.data.is_instructor isInstructor.value = userResource.data.is_instructor
addPrograms() addPrograms()
addProgrammingExercises()
addQuizzes() addQuizzes()
addAssignments() addAssignments()
setUpOnboarding() setUpOnboarding()

View File

@@ -1,7 +1,7 @@
<template> <template>
<div <div
v-if="course.title" v-if="course.title"
class="flex flex-col h-full rounded-md border-2 overflow-auto hover:border hover:border-outline-gray-3 text-ink-gray-9" class="flex flex-col h-full rounded-md border-2 overflow-auto text-ink-gray-9"
style="min-height: 350px" style="min-height: 350px"
> >
<div <div
@@ -28,7 +28,7 @@
<div <div
v-if="course.tags" v-if="course.tags"
v-for="tag in course.tags?.split(', ')" v-for="tag in course.tags?.split(', ')"
class="text-xs bg-surface-white text-ink-gray-9 px-2 py-0.5 rounded-md mb-1 mr-1" class="text-xs border bg-surface-white text-ink-gray-9 px-2 py-0.5 rounded-md mb-1 mr-1"
> >
{{ tag }} {{ tag }}
</div> </div>

View File

@@ -15,7 +15,7 @@
</div> --> </div> -->
<FormControl <FormControl
v-model="searchFilter" v-model="searchFilter"
:placeholder="__('Search by Member Name')" :placeholder="__('Search by Member')"
type="text" type="text"
class="w-full" class="w-full"
/> />
@@ -149,6 +149,10 @@ import { theme } from '@/utils/theme'
const show = defineModel<boolean | undefined>() const show = defineModel<boolean | undefined>()
const searchFilter = ref<string | null>(null) const searchFilter = ref<string | null>(null)
type Filters = {
course: string | undefined
member_name?: string[]
}
const props = defineProps<{ const props = defineProps<{
courseName?: string courseName?: string
@@ -184,10 +188,6 @@ const progressList = createListResource({
watch([searchFilter], () => { watch([searchFilter], () => {
let filterApplied = false let filterApplied = false
type Filters = {
course: string | undefined
member_name?: string[]
}
let filters: Filters = { let filters: Filters = {
course: props.courseName, course: props.courseName,
} }

View File

@@ -8,13 +8,20 @@
> >
<template #body-content> <template #body-content>
<div class="text-base"> <div class="text-base">
<TabButtons <div class="flex items-center justify-between">
v-if="tabs.length > 1" <TabButtons
:buttons="tabs" v-if="tabs.length > 1"
v-model="currentTab" :buttons="tabs"
class="w-fit" v-model="currentTab"
/> class="w-fit"
<div v-if="currentTab" class="mt-8"> />
<!-- <FormControl
v-model="searchText"
:placeholder="__('Search by Member')"
class="mt-2 mr-5 w-[25%]"
/> -->
</div>
<div v-if="currentTab" class="mt-4">
<div class="grid grid-cols-[55%,40%] gap-5"> <div class="grid grid-cols-[55%,40%] gap-5">
<div class="space-y-5 border rounded-md p-2 pt-4"> <div class="space-y-5 border rounded-md p-2 pt-4">
<div class="grid grid-cols-[70%,30%] text-sm text-ink-gray-5"> <div class="grid grid-cols-[70%,30%] text-sm text-ink-gray-5">
@@ -52,7 +59,7 @@
</div> </div>
</div> </div>
<div class="text-center text-sm"> <div class="text-center text-sm">
{{ parseFloat(row.watch_time).toFixed(2) }} {{ convertToMinutes(row.watch_time) }}
</div> </div>
</div> </div>
</router-link> </router-link>
@@ -62,7 +69,7 @@
<NumberChart <NumberChart
class="border rounded-md" class="border rounded-md"
:config="{ :config="{
title: __('Average Watch Time (seconds)'), title: __('Average Watch Time (minutes)'),
value: averageWatchTime, value: averageWatchTime,
}" }"
/> />
@@ -73,6 +80,9 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else class="text-sm text-ink-gray-5">
{{ __('No statistics available for this video.') }}
</div>
</div> </div>
</template> </template>
</Dialog> </Dialog>
@@ -82,15 +92,21 @@ import {
Avatar, Avatar,
createListResource, createListResource,
Dialog, Dialog,
FormControl,
NumberChart, NumberChart,
TabButtons, TabButtons,
} from 'frappe-ui' } from 'frappe-ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { enablePlyr } from '@/utils' import { enablePlyr, convertToMinutes } from '@/utils'
import VideoBlock from '@/components/VideoBlock.vue' import VideoBlock from '@/components/VideoBlock.vue'
const show = defineModel<boolean | undefined>() const show = defineModel<boolean | undefined>()
const currentTab = ref<string>('') const currentTab = ref<string>('')
const searchText = ref<string>('')
type Filters = {
lesson: string | undefined
member_name?: string[]
}
const props = defineProps<{ const props = defineProps<{
lessonName?: string lessonName?: string
@@ -127,6 +143,24 @@ watch(
} }
) )
watch(searchText, () => {
let filterApplied = false
let filters: Filters = {
lesson: props.lessonName,
}
if (searchText.value) {
filters.member_name = ['like', `%${searchText.value}%`]
filterApplied = true
}
statistics.update({
filters: filters,
})
statistics.reload({})
})
watch(show, () => { watch(show, () => {
if (show.value) { if (show.value) {
enablePlyr() enablePlyr()
@@ -151,7 +185,7 @@ const averageWatchTime = computed(() => {
totalWatchTime += parseFloat(item.watch_time) totalWatchTime += parseFloat(item.watch_time)
}) })
return totalWatchTime / currentTabData.value.length return convertToMinutes(totalWatchTime / currentTabData.value.length)
}) })
const currentTabData = computed(() => { const currentTabData = computed(() => {

View File

@@ -132,6 +132,7 @@ const participants = createListResource({
doctype: 'LMS Certificate', doctype: 'LMS Certificate',
url: 'lms.lms.api.get_certified_participants', url: 'lms.lms.api.get_certified_participants',
start: 0, start: 0,
cache: ['certified_participants'],
pageLength: 100, pageLength: 100,
}) })

View File

@@ -420,17 +420,6 @@ export function getSidebarLinks() {
to: 'Batches', to: 'Batches',
activeFor: ['Batches', 'BatchDetail', 'Batch', 'BatchForm'], activeFor: ['Batches', 'BatchDetail', 'Batch', 'BatchForm'],
}, },
{
label: 'Programming Exercises',
icon: 'Code',
to: 'ProgrammingExercises',
activeFor: [
'ProgrammingExercises',
'ProgrammingExerciseForm',
'ProgrammingExerciseSubmissions',
'ProgrammingExerciseSubmission',
],
},
{ {
label: 'Certified Members', label: 'Certified Members',
icon: 'GraduationCap', icon: 'GraduationCap',
@@ -678,3 +667,9 @@ export const formatTimestamp = (seconds) => {
const secs = String(date.getUTCSeconds()).padStart(2, '0') const secs = String(date.getUTCSeconds()).padStart(2, '0')
return `${minutes}:${secs}` return `${minutes}:${secs}`
} }
export const convertToMinutes = (seconds) => {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = Math.round(seconds % 60)
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`
}

View File

@@ -13,7 +13,7 @@ dependencies = [
"markdown~=3.5.1", "markdown~=3.5.1",
"beautifulsoup4~=4.12.2", "beautifulsoup4~=4.12.2",
"lxml~=4.9.3", "lxml~=4.9.3",
"cairocffi~=1.6.1", "cairocffi==1.5.1",
"razorpay~=1.4.1", "razorpay~=1.4.1",
"fuzzywuzzy~=0.18.0", "fuzzywuzzy~=0.18.0",
] ]