feat: rating component

This commit is contained in:
Jannat Patel
2023-12-20 10:35:12 +05:30
parent fbe219a888
commit eb3afbbad1
10 changed files with 267 additions and 88 deletions

View File

@@ -2,16 +2,28 @@
<div class="shadow rounded-md" style="width: 300px;">
<iframe v-if="course.data.video_link" :src="video_link" class="rounded-t-md" />
<div class="p-5">
<Button v-if="course.data.membership" variant="solid" class="w-full mb-3">
<span>
{{ __("Continue Learning") }}
</span>
</Button>
<Button v-else variant="solid" class="w-full mb-3" >
<router-link v-if="course.data.membership && course.data.current_lesson"
:to="{name: 'Lesson', params: {
courseName: course.name,
chapterNumber: course.data.current_lesson.split('.')[0],
lessonNumber: course.data.current_lesson.split('.')[1]
}}">
<Button variant="solid" class="w-full mb-3">
<span>
{{ __("Continue Learning") }}
</span>
</Button>
</router-link>
<Button v-else @click="enrollStudent()" variant="solid" class="w-full mb-3">
<span>
{{ __("Start Learning") }}
</span>
</Button>
<Button v-if="user?.data?.is_moderator" variant="subtle" class="w-full mb-3">
<span>
{{ __("Edit") }}
</span>
</Button>
<div class="flex items-center mb-3">
<Users class="h-4 w-4 text-gray-700"/>
<span class="ml-1">
@@ -35,8 +47,14 @@
</template>
<script setup>
import { BookOpen, Users, Star } from 'lucide-vue-next'
import { computed } from 'vue'
import { Button } from "frappe-ui"
import { computed, inject } from 'vue'
import { Button, createResource } from "frappe-ui"
import { createToast } from "@/utils/"
import { useRouter } from 'vue-router'
const router = useRouter()
const user = inject("$user");
const props = defineProps({
course: {
type: Object,
@@ -50,4 +68,34 @@ const video_link = computed(() => {
}
return null;
});
function enrollStudent() {
if (!user.data) {
createToast({
title: "Please Login",
icon: 'alert-circle',
iconClasses: 'text-yellow-600 bg-yellow-100',
})
setTimeout(() => {
window.location.href = `/login?redirect-to=${window.location.pathname}`
}, 3000)
} else {
const enrollStudentResource = createResource({
url: "lms.lms.doctype.lms_enrollment.lms_enrollment.create_membership"
})
console.log(props.course)
enrollStudentResource.submit({
course: props.course.data.name
}).then(() => {
createToast({
title: "Enrolled Successfully",
icon: 'check',
iconClasses: 'text-green-600 bg-green-100',
})
setTimeout(() => {
router.push({ name: 'Lesson', params: { courseName: props.course.data.name, chapterNumber: 1, lessonNumber: 1 } })
}, 3000)
})
}
}
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div class="text-base">
<div class="mt-4">
<Disclosure v-slot="{ open }" v-for="(chapter, index) in outline.data" :key="chapter.name">
<Disclosure v-slot="{ open }" v-for="(chapter, index) in outline.data" :key="chapter.name" :defaultOpen="index == 0">
<DisclosureButton class="flex w-full px-2 pt-2 pb-3">
<ChevronRight
:class="{'rotate-90 transform duration-200' : open, 'duration-200' : !open, 'open': index == 1}"
@@ -11,7 +11,7 @@
{{ chapter.title }}
</div>
</DisclosureButton>
<DisclosurePanel class="px-10 pb-4" :static="index == 0">
<DisclosurePanel class="px-10 pb-4">
<div v-for="lesson in chapter.lessons" :key="lesson.name">
<div class="flex items-center text-base mb-3">
<MonitorPlay v-if="lesson.icon === 'icon-youtube'" class="h-4 w-4 text-gray-900 stroke-1 mr-2"/>

View File

@@ -14,7 +14,7 @@
<div class="mb-2">
{{ reviews.data.length }} {{ __("reviews") }}
</div>
<Button>
<Button @click="openReviewModal()">
<span>
{{ __("Write a review") }}
</span>
@@ -53,11 +53,8 @@
<Star v-for="index in 5" class="h-5 w-5 text-gray-100 bg-gray-200 rounded-sm mr-2" :class="(index <= Math.ceil(review.rating)) ? 'fill-orange-500' : 'fill-gray-600'"/>
</div>
</div>
<div>
</div>
</div>
<div class="mt-4">
<div class="mt-4 leading-5">
{{ review.review }}
</div>
</div>
@@ -65,12 +62,15 @@
</div>
</div>
</div>
{{ showReviewModal }}
<ReviewModal v-model="showReviewModal"/>
</template>
<script setup>
import { Star } from 'lucide-vue-next'
import { createResource, Button } from "frappe-ui";
import { computed } from "vue";
import { computed, ref } from "vue";
import UserAvatar from '@/components/UserAvatar.vue';
import ReviewModal from '@/components/ReviewModal.vue';
const props = defineProps({
courseName: {
@@ -111,4 +111,10 @@ const rating_percent = computed(() => {
});
return rating_percent;
});
const showReviewModal = ref(false)
function openReviewModal() {
console.log("called")
showReviewModal.value = true;
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<div class="flex text-center">
<div v-for="index in 5">
<Star class="h-5 w-5 fill-gray-400 text-gray-200 mr-1 cursor-pointer hover:fill-orange-500" @input="handleChange"/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, useAttrs } from 'vue'
import { Star } from 'lucide-vue-next'
const props = defineProps({
id: {
type: String,
default: '',
},
modelValue: {
type: String,
default: '',
},
})
const emit = defineEmits(['update:modelValue'])
const attrs = useAttrs()
let emitChange = (value: string) => {
emit('update:modelValue', value)
}
let handleChange = (e: Event) => {
emitChange((e.target as HTMLInputElement).value)
}
</script>

View File

@@ -0,0 +1,26 @@
<template>
<Dialog v-model="show" :options='{
title: __("Write a Review"),
size: "xl",
actions: [
{
label: "Submit",
variant: "solid",
onClick: ({ close }) => submitReview(close)
}
]
}'>
<template #body-content>
<Textarea type="text" size="md" rows="5" />
<Rating />
</template>
</Dialog>
</template>
<script setup>
import { Dialog, Textarea } from 'frappe-ui'
import { defineModel } from "vue"
import Rating from '@/components/Rating.vue';
const show = defineModel()
console.log(show)
</script>

View File

@@ -1,58 +1,64 @@
<template>
<div v-if="courses.data" class="h-screen">
<header class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5">
<Breadcrumbs class="h-7" :items="[{ label: __('All Courses'), route: { name: 'Courses' } }]" />
<div class="flex">
<Button variant="solid">
<template #prefix>
<Plus class="h-4 w-4" />
<div class="h-screen">
<div v-if="courses.data">
<header class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5">
<Breadcrumbs class="h-7" :items="[{ label: __('All Courses'), route: { name: 'Courses' } }]" />
<div class="flex">
<Button variant="solid">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
{{ __("New Course") }}
</Button>
</div>
</header>
<div class="mx-5 py-5">
<Tabs class="overflow-hidden" v-model="tabIndex" :tabs="tabs">
<template #tab="{ tab, selected }">
<div>
<button
class="group -mb-px flex items-center gap-2 border-b border-transparent py-2.5 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900"
:class="{ 'text-gray-900': selected }">
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
{{ __(tab.label) }}
<Badge :class="{ 'text-gray-900 border border-gray-900': selected }" variant="subtle" theme="gray"
size="sm">
{{ tab.count }}
</Badge>
</button>
</div>
</template>
{{ __("New Course") }}
</Button>
</div>
</header>
<div class="mx-5 py-5">
<Tabs class="overflow-hidden" v-model="tabIndex" :tabs="tabs">
<template #tab="{ tab, selected }">
<div>
<button
class="group -mb-px flex items-center gap-2 border-b border-transparent py-2.5 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900"
:class="{ 'text-gray-900': selected }">
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
{{ __(tab.label) }}
<Badge :class="{ 'text-gray-900 border border-gray-900': selected }" variant="subtle" theme="gray"
size="sm">
{{ tab.count }}
</Badge>
</button>
</div>
</template>
<template #default="{ tab }">
<div v-if="tab.courses && tab.courses.value.length" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5">
<router-link v-for="course in tab.courses.value"
:to="
course.membership && course.current_lesson
? {name: 'Lesson', params: {
courseName: course.name,
chapterNumber: course.current_lesson.split('.')[0],
lessonNumber: course.current_lesson.split('.')[1] }}
: course.membership ? { name: 'Lesson', params: {
courseName: course.name,
chapterNumber: 1,
lessonNumber: 1 }
} : { name: 'CourseDetail', params: { courseName: course.name } }">
<CourseCard :course="course" />
</router-link>
</div>
<div v-else class="grid flex-1 place-items-center text-xl font-medium text-gray-500">
<div class="flex flex-col items-center justify-center mt-4">
<div>
{{ __("No {0} courses found").format(tab.label.toLowerCase()) }}
<template #default="{ tab }">
<div v-if="tab.courses && tab.courses.value.length" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5">
<router-link v-for="course in tab.courses.value"
:to="course.membership && course.current_lesson
? {
name: 'Lesson', params: {
courseName: course.name,
chapterNumber: course.current_lesson.split('.')[0],
lessonNumber: course.current_lesson.split('.')[1]
}
}
: course.membership ? {
name: 'Lesson', params: {
courseName: course.name,
chapterNumber: 1,
lessonNumber: 1
}
} : { name: 'CourseDetail', params: { courseName: course.name } }">
<CourseCard :course="course" />
</router-link>
</div>
<div v-else class="grid flex-1 place-items-center text-xl font-medium text-gray-500">
<div class="flex flex-col items-center justify-center mt-4">
<div>
{{ __("No {0} courses found").format(tab.label.toLowerCase()) }}
</div>
</div>
</div>
</div>
</template>
</Tabs>
</template>
</Tabs>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,35 @@
import { Dialog, ErrorMessage } from 'frappe-ui'
import { h, reactive, ref } from 'vue'
let dialogs = ref([])
export let Dialogs = {
name: 'Dialogs',
render() {
return dialogs.value.map((dialog) => {
return h(
Dialog,
{
options: dialog,
modelValue: dialog.show,
'onUpdate:modelValue': (val) => (dialog.show = val),
},
() => [
h(
'p',
{ class: 'text-p-base text-gray-700' },
dialog.message
),
h(ErrorMessage, { class: 'mt-2', message: dialog.error }),
]
)
})
},
}
export function createDialog(options) {
let dialog = reactive(options)
dialog.key = `dialog-${Math.random().toString(36).slice(2, 9)}`
dialogs.value.push(dialog)
dialog.show = true
}

View File

@@ -0,0 +1,8 @@
import { toast } from 'frappe-ui'
export function createToast(options) {
toast({
position: 'bottom-right',
...options,
})
}