Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: batch feedback #1241

Merged
merged 4 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions frontend/src/components/BatchFeedback.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
<template>
<div v-if="user.data?.is_student">
<div
v-if="feedbackList.data?.length"
class="bg-blue-100 text-blue-700 p-2 rounded-md mb-5"
>
{{ __('Thank you for providing your feedback!') }}
</div>
<div v-else class="flex justify-between items-center mb-5">
<div class="text-lg font-semibold">
{{ __('Help Us Improve') }}
</div>
<Button @click="submitFeedback()">
{{ __('Submit') }}
</Button>
</div>
<div class="space-y-8">
<div class="flex items-center justify-between">
<Rating
v-model="feedback.content"
:label="__('Content')"
:readonly="readOnly"
/>
<Rating
v-model="feedback.delivery"
:label="__('Delivery')"
:readonly="readOnly"
/>
<Rating
v-model="feedback.instructors"
:label="__('Instructors')"
:readonly="readOnly"
/>
<Rating
v-model="feedback.value"
:label="__('Value')"
:readonly="readOnly"
/>
</div>
<FormControl
v-model="feedback.feedback"
type="textarea"
:label="__('Feedback')"
:rows="7"
:readonly="readOnly"
/>
</div>
</div>

<div v-else-if="feedbackList.data?.length">
<div class="text-lg font-semibold mb-5">
{{ __('Average of Feedback Received') }}
</div>

<div class="flex items-center justify-between mb-10">
<Rating
v-model="average.content"
:label="__('Content')"
:readonly="true"
/>
<Rating
v-model="average.delivery"
:label="__('Delivery')"
:readonly="true"
/>
<Rating
v-model="average.instructors"
:label="__('Instructors')"
:readonly="true"
/>
<Rating v-model="average.value" :label="__('Value')" :readonly="true" />
</div>

<div class="text-lg font-semibold mb-5">
{{ __('All Feedback') }}
</div>
<ListView
:columns="feedbackColumns"
:rows="feedbackList.data"
row-key="name"
:options="{
showTooltip: false,
rowHeight: 'h-16',
selectable: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-gray-100 p-2"
></ListHeader>
<ListRows>
<ListRow
:row="row"
v-for="row in feedbackList.data"
class="group cursor-pointer"
>
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="text-sm"
>
<template #prefix>
<div v-if="column.key == 'member_name'">
<Avatar
class="flex items-center"
:image="row['member_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div v-if="ratingKeys.includes(column.key)">
<Rating v-model="row[column.key]" :readonly="true" />
</div>
<div v-else class="leading-5">
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
</ListView>
</div>
</template>
<script setup>
import { computed, inject, onMounted, reactive, ref, watch } from 'vue'
import {
Avatar,
Button,
createListResource,
FormControl,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListRowItem,
Rating,
} from 'frappe-ui'

const user = inject('$user')
const ratingKeys = ['content', 'delivery', 'instructors', 'value']
const readOnly = ref(false)
const average = reactive({})
const feedback = reactive({})

const props = defineProps({
batch: {
type: String,
required: true,
},
})

onMounted(() => {
let filters = {
batch: props.batch,
}
if (user.data?.is_student) {
filters['member'] = user.data?.name
}
feedbackList.update({
filters: filters,
})
feedbackList.reload()
})

const feedbackList = createListResource({
doctype: 'LMS Batch Feedback',
filters: {
batch: props.batch,
},
fields: [
'content',
'delivery',
'instructors',
'value',
'feedback',
'name',
'member',
'member_name',
'member_image',
],
cache: ['feedbackList', props.batch, user.data?.name],
})

watch(
() => feedbackList.data,
() => {
if (feedbackList.data.length) {
let data = feedbackList.data
readOnly.value = true

ratingKeys.forEach((key) => {
average[key] = 0
})

data.forEach((row) => {
Object.keys(row).forEach((key) => {
if (ratingKeys.includes(key)) row[key] = row[key] * 5
feedback[key] = row[key]
})
ratingKeys.forEach((key) => {
average[key] += row[key]
})
})
Object.keys(average).forEach((key) => {
average[key] = average[key] / data.length
})
}
}
)

const submitFeedback = () => {
ratingKeys.forEach((key) => {
feedback[key] = feedback[key] / 5
})
feedbackList.insert.submit(
{
member: user.data?.name,
batch: props.batch,
...feedback,
},
{
onSuccess: () => {
feedbackList.reload()
},
}
)
}

const feedbackColumns = computed(() => {
return [
{
label: 'Member',
key: 'member_name',
width: '10rem',
},
{
label: 'Feedback',
key: 'feedback',
width: '15rem',
},
{
label: 'Content',
key: 'content',
width: '10rem',
},
{
label: 'Delivery',
key: 'delivery',
width: '10rem',
},
{
label: 'Instructors',
key: 'instructors',
width: '10rem',
},
{
label: 'Value',
key: 'value',
width: '10rem',
},
]
})
</script>
2 changes: 1 addition & 1 deletion frontend/src/components/BatchStudents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ const getChartOptions = (categories) => {
},
rotate: 0,
formatter: function (value) {
return value.length > 30 ? `${value.substring(0, 30)}...` : value // Trim long labels
return value.length > 30 ? `${value.substring(0, 30)}...` : value
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/JobCard.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="flex rounded p-1 lg:px-2 lg:py-2.5 hover:bg-gray-100">
<div class="flex rounded p-1 lg:px-2 lg:py-4 hover:bg-gray-100">
<div class="flex w-3/5 md:w-2/5">
<img
:src="job.company_logo"
Expand Down
20 changes: 14 additions & 6 deletions frontend/src/pages/Batch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</Button>
</div>
</header>
<div v-if="batch.data" class="grid grid-cols-[70%,30%] h-screen">
<div v-if="batch.data" class="grid grid-cols-[75%,25%] h-screen">
<div class="border-r">
<Tabs
v-model="tabIndex"
Expand Down Expand Up @@ -65,7 +65,7 @@
<div v-else-if="tab.label == 'Dashboard'">
<BatchStudents :batch="batch.data" />
</div>
<div v-else-if="tab.label == 'Live Class'">
<div v-else-if="tab.label == 'Classes'">
<LiveClass :batch="batch.data.name" />
</div>
<div v-else-if="tab.label == 'Assessments'">
Expand All @@ -81,9 +81,12 @@
:title="__('Discussions')"
:key="batch.data.name"
:singleThread="true"
:scrollToBottom="true"
:scrollToBottom="false"
/>
</div>
<div v-else-if="tab.label == 'Feedback'">
<BatchFeedback :batch="batch.data.name" />
</div>
</div>
</template>
</Tabs>
Expand Down Expand Up @@ -190,12 +193,11 @@ import {
BookOpen,
Laptop,
BookOpenCheck,
Contact2,
Mail,
SendIcon,
MessageCircle,
Globe,
ShieldCheck,
ClipboardPen,
} from 'lucide-vue-next'
import { formatTime, updateDocumentTitle } from '@/utils'
import BatchDashboard from '@/components/BatchDashboard.vue'
Expand All @@ -208,6 +210,7 @@ import AnnouncementModal from '@/components/Modals/AnnouncementModal.vue'
import Discussions from '@/components/Discussions.vue'
import DateRange from '@/components/Common/DateRange.vue'
import BulkCertificates from '@/components/Modals/BulkCertificates.vue'
import BatchFeedback from '@/components/BatchFeedback.vue'

const user = inject('$user')
const showAnnouncementModal = ref(false)
Expand Down Expand Up @@ -271,7 +274,7 @@ const tabs = computed(() => {
})

batchTabs.push({
label: 'Live Class',
label: 'Classes',
icon: Laptop,
})

Expand All @@ -291,6 +294,11 @@ const tabs = computed(() => {
label: 'Discussions',
icon: MessageCircle,
})

batchTabs.push({
label: 'Feedback',
icon: ClipboardPen,
})
return batchTabs
})

Expand Down
10 changes: 7 additions & 3 deletions lms/lms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,9 +603,13 @@ def get_categories(doctype, filters):
@frappe.whitelist()
def get_members(start=0, search=""):
"""Get members for the given search term and start index.
Args: start (int): Start index for the query.
search (str): Search term to filter the results.
Returns: List of members.
Args: start (int): Start index for the query.
<<<<<<< HEAD
search (str): Search term to filter the results.
=======
search (str): Search term to filter the results.
>>>>>>> 4869bba7bbb2fb38477d6fc29fb3b5838e075577
Returns: List of members.
"""

filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]}
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions lms/lms/doctype/lms_batch_feedback/lms_batch_feedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2025, Frappe and contributors
// For license information, please see license.txt

// frappe.ui.form.on("LMS Batch Feedback", {
// refresh(frm) {

// },
// });
Loading
Loading