Files
lms/frontend/src/components/Modals/VideoStatistics.vue
2025-07-01 16:55:55 +05:30

217 lines
4.6 KiB
Vue

<template>
<Dialog
v-model="show"
:options="{
size: '4xl',
title: __('Video Statistics for {0}').format(lessonTitle),
}"
>
<template #body-content>
<div class="text-base">
<TabButtons
v-if="tabs.length > 1"
:buttons="tabs"
v-model="currentTab"
class="w-fit"
/>
<div v-if="currentTab" class="mt-8">
<div class="grid grid-cols-[55%,40%] gap-5">
<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="px-4">
{{ __('Member') }}
</div>
<div class="text-center">
{{ __('Watch Time') }}
</div>
</div>
<div
v-for="row in currentTabData"
class="hover:bg-surface-gray-1 cursor-pointer rounded-md py-1 px-2"
>
<router-link
:to="{
name: 'Profile',
params: { username: row.member_username },
}"
>
<div class="grid grid-cols-[70%,30%] items-center">
<div class="flex items-center space-x-2">
<Avatar
:image="row.member_image"
:label="row.member_name"
size="xl"
/>
<div class="space-y-1">
<div class="font-medium">
{{ row.member_name }}
</div>
<div class="text-sm text-ink-gray-6">
{{ row.member }}
</div>
</div>
</div>
<div class="text-center text-sm">
{{ parseFloat(row.watch_time).toFixed(2) }}
</div>
</div>
</router-link>
</div>
</div>
<div class="space-y-5">
<NumberChart
class="border rounded-md"
:config="{
title: __('Average Watch Time (seconds)'),
value: averageWatchTime,
}"
/>
<div v-if="isPlyrSource">
<div class="video-player" :src="currentTab"></div>
</div>
<VideoBlock v-else :file="currentTab" />
</div>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Avatar,
createListResource,
Dialog,
NumberChart,
TabButtons,
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import { enablePlyr } from '@/utils'
import VideoBlock from '@/components/VideoBlock.vue'
const show = defineModel<boolean | undefined>()
const currentTab = ref<string>('')
const props = defineProps<{
lessonName?: string
lessonTitle?: string
}>()
const statistics = createListResource({
doctype: 'LMS Video Watch Duration',
filters: {
lesson: props.lessonName,
},
fields: [
'name',
'member',
'member_name',
'member_image',
'member_username',
'source',
'watch_time',
],
cache: ['videoStatistics', props.lessonName],
onSuccess() {
currentTab.value = Object.keys(statisticsData.value)[0]
},
})
watch(
() => props.lessonName,
() => {
if (props.lessonName) {
statistics.filters.lesson = props.lessonName
statistics.reload()
}
}
)
watch(show, () => {
if (show.value) {
enablePlyr()
}
})
const statisticsData = computed(() => {
const grouped = <Record<string, any[]>>{}
statistics.data.forEach((item: { source: string }) => {
if (!grouped[item.source]) {
grouped[item.source] = []
}
grouped[item.source].push(item)
})
return grouped
})
const averageWatchTime = computed(() => {
let totalWatchTime = 0
currentTabData.value.forEach((item: { watch_time: string }) => {
totalWatchTime += parseFloat(item.watch_time)
})
return totalWatchTime / currentTabData.value.length
})
const currentTabData = computed(() => {
return statisticsData.value[currentTab.value] || []
})
const isPlyrSource = computed(() => {
return (
currentTab.value.includes('youtube') || currentTab.value.includes('vimeo')
)
})
const provider = computed(() => {
if (currentTab.value.includes('youtube')) {
return 'youtube'
} else if (currentTab.value.includes('vimeo')) {
return 'vimeo'
}
return ''
})
const embedURL = computed(() => {
if (isPlyrSource.value) {
return currentTab.value.replace('watch?v=', 'embed/')
}
return ''
})
const tabs = computed(() => {
return Object.keys(statisticsData.value).map((source, index) => ({
label: __(`Video ${index + 1}`),
value: source,
}))
})
</script>
<style>
.plyr__volume input[type='range'] {
display: none;
}
.plyr__control--overlaid {
background: radial-gradient(
circle,
rgba(0, 0, 0, 0.4) 0%,
rgba(0, 0, 0, 0.5) 50%
);
}
.plyr__control:hover {
background: none;
}
.plyr--video {
border: 1px solid theme('colors.gray.200');
border-radius: 8px;
}
:root {
--plyr-range-fill-background: white;
--plyr-video-control-background-hover: transparent;
}
</style>