fix: show video statistics watch time in minutes
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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')}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user