feat: upcoming evals

This commit is contained in:
Jannat Patel
2024-01-08 16:17:50 +05:30
parent 3a33f047f5
commit 09ae61492f
48 changed files with 33659 additions and 24452 deletions

View File

@@ -12,7 +12,6 @@
"frappe-ui": "^0.1.16",
"lucide-vue-next": "^0.259.0",
"pinia": "^2.0.33",
"qalendar": "^3.6.1",
"tailwindcss": "^3.2.7",
"vue": "^3.2.25",
"dayjs": "^1.11.6",

View File

@@ -0,0 +1,47 @@
<template>
<div>
<div class="text-lg font-semibold mb-4">
{{ __('Assessments') }}
</div>
<div v-if="assessments?.length">
<ListView
:columns="getAssessmentColumns()"
:rows="attempts?.data"
row-key="name"
:options="{ selectable: false, showTooltip: false }"
>
</ListView>
</div>
<div v-else class="text-sm italic text-gray-600">
{{ __('No Assessments') }}
</div>
</div>
</template>
<script setup>
import { ListView } from 'frappe-ui'
const props = defineProps({
assessments: {
type: Array,
default: [],
},
})
const getSubmissionColumns = () => {
return [
{
label: 'Assessment',
key: 'title',
},
{
label: 'Type',
key: 'type',
},
{
label: 'Status/Score',
key: 'status',
align: 'center',
},
]
}
</script>

View File

@@ -29,7 +29,7 @@
</div>
<div class="flex items-center mb-3">
<BookOpen class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span> {{ batch.courses }} {{ __('Courses') }} </span>
<span> {{ batch.courses.length }} {{ __('Courses') }} </span>
</div>
<div class="flex items-center mb-3">
<Calendar class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />

View File

@@ -0,0 +1,17 @@
<template>
<div>
<UpcomingEvaluations :upcoming_evals="batch.data.upcoming_evals" />
<Assessments :assessments="batch.data.assessments" />
</div>
</template>
<script setup>
import UpcomingEvaluations from './UpcomingEvaluations.vue'
import Assessments from './Assessments.vue'
const props = defineProps({
batch: {
type: Object,
default: null,
},
})
</script>

View File

@@ -1,38 +1,38 @@
<template>
<div class="shadow rounded-md p-5" style="width: 300px">
<div v-if="batch.data" class="shadow rounded-md p-5" style="width: 300px">
<Badge
v-if="batch.doc.seat_count && seats_left > 0"
v-if="batch.data.seat_count && seats_left > 0"
theme="green"
class="self-start mb-2 float-right"
>
{{ seats_left }} {{ __('Seat Left') }}
</Badge>
<Badge
v-else-if="batch.doc.seat_count && seats_left <= 0"
v-else-if="batch.data.seat_count && seats_left <= 0"
theme="red"
class="self-start mb-2 float-right"
>
{{ __('Sold Out') }}
</Badge>
<div v-if="batch.doc.amount" class="text-lg font-semibold mb-3">
{{ formatNumberIntoCurrency(batch.doc.amount, batch.doc.currency) }}
<div v-if="batch.data.amount" class="text-lg font-semibold mb-3">
{{ formatNumberIntoCurrency(batch.data.amount, batch.data.currency) }}
</div>
<div class="flex items-center mb-3">
<BookOpen class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span> {{ batch.doc.courses.length }} {{ __('Courses') }} </span>
<span> {{ batch.data.courses.length }} {{ __('Courses') }} </span>
</div>
<div class="flex items-center mb-3">
<Calendar class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span>
{{ dayjs(batch.doc.start_date).format('DD MMM YYYY') }} -
{{ dayjs(batch.doc.end_date).format('DD MMM YYYY') }}
{{ dayjs(batch.data.start_date).format('DD MMM YYYY') }} -
{{ dayjs(batch.data.end_date).format('DD MMM YYYY') }}
</span>
</div>
<div class="flex items-center">
<Clock class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span>
{{ formatTime(batch.doc.start_time) }} -
{{ formatTime(batch.doc.end_time) }}
{{ formatTime(batch.data.start_time) }} -
{{ formatTime(batch.data.end_time) }}
</span>
</div>
<Button v-if="user?.data?.is_moderator" class="w-full mt-4">
@@ -41,7 +41,7 @@
</span>
</Button>
<Button
v-else-if="batch.doc.paid_batch"
v-else-if="batch.data.paid_batch"
class="w-full mt-4"
variant="solid"
>
@@ -73,8 +73,8 @@ const props = defineProps({
})
const seats_left = computed(() => {
if (props.batch.doc.seat_count) {
return props.batch.doc.seat_count - props.batch.doc.students.length
if (props.batch.data?.seat_count) {
return props.batch.data?.seat_count - props.batch.data?.students?.length
}
return null
})

View File

@@ -114,35 +114,13 @@ import { Badge, createResource } from 'frappe-ui'
import { ref, watchEffect } from 'vue'
const { user } = sessionStore()
let course = ref({})
const props = defineProps({
course: {
type: [Object, String],
type: Object,
default: null,
},
})
const courseDetails = createResource({
url: 'lms.lms.utils.get_course_details',
cache: ['course', props.courseName],
makeParams() {
return {
course: props.course,
}
},
transform(data) {
course.value = data
},
})
/* watchEffect(() => {
if (props.course && typeof props.course === "object") {
course.value = props.course;
} else {
courseDetails.reload();
}
}); */
</script>
<style>
.course-image {

View File

@@ -0,0 +1,53 @@
<template>
<div class="mb-10">
<div class="text-lg font-semibold mb-4">
{{ __('Upcoming Evaluations') }}
</div>
<div v-if="upcoming_evals.length">
<div class="grid grid-cols-2">
<div v-for="evl in upcoming_evals">
<div class="border rounded-md p-3">
<div class="font-medium mb-3">
{{ evl.course_title }}
</div>
<div class="flex items-center mb-2">
<Calendar class="w-4 h-4 stroke-1.5" />
<span class="ml-2">
{{ dayjs(evl.date).format('DD MMMM YYYY') }}
</span>
</div>
<div class="flex items-center mb-2">
<Clock class="w-4 h-4 stroke-1.5" />
<span class="ml-2">
{{ formatTime(evl.start_time) }}
</span>
</div>
<div class="flex items-center">
<UserCog2 class="w-4 h-4 stroke-1.5" />
<span class="ml-2">
{{ evl.evaluator_name }}
</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="text-sm italic text-gray-600">
{{ __('No upcoming evaluations.') }}
</div>
</div>
</template>
<script setup>
import { Calendar, Clock, UserCog2 } from 'lucide-vue-next'
import { inject } from 'vue'
import { formatTime } from '../utils'
const dayjs = inject('$dayjs')
const props = defineProps({
upcoming_evals: {
type: Array,
default: [],
},
})
</script>

View File

@@ -1,44 +1,135 @@
<template>
<div class="h-screen text-base">
<div v-if="user.data?.is_moderator || is_student" class="h-screen text-base">
<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="breadcrumbs" />
</header>
<div v-if="batch.doc">
<div v-if="batch.data">
<div class="grid grid-cols-[70%,30%] h-full">
<div class="border-r-2"></div>
<div class="border-r-2">
<Tabs class="overflow-hidden" v-model="tabIndex" :tabs="tabs">
<template #tab="{ tab, selected }">
<div>
<button
class="group -mb-px flex items-center gap-1 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-4 stroke-1.5"
/>
{{ __(tab.label) }}
<Badge
v-if="tab.count"
: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 class="p-10">
<div v-if="tab.label == 'Courses'">
<div class="text-xl font-semibold">
{{ __('Courses') }}
</div>
<div
class="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 gap-8 mt-5"
>
<div v-for="course in courses.data">
<router-link
:to="{
name: 'CourseDetail',
params: {
courseName: course.name,
},
}"
>
<CourseCard :key="course.name" :course="course" />
</router-link>
</div>
</div>
</div>
<div v-else-if="tab.label == 'Dashboard'">
<BatchDashboard :batch="batch" />
</div>
</div>
</template>
</Tabs>
</div>
<div class="p-5">
<div class="text-2xl font-semibold mb-3">
{{ batch.doc.title }}
{{ batch.data.title }}
</div>
<div class="flex items-center mb-2">
<div class="flex items-center mb-3">
<Calendar class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span>
{{ dayjs(batch.doc.start_date).format('DD MMM YYYY') }} -
{{ dayjs(batch.doc.end_date).format('DD MMM YYYY') }}
{{ dayjs(batch.data.start_date).format('DD MMM YYYY') }} -
{{ dayjs(batch.data.end_date).format('DD MMM YYYY') }}
</span>
</div>
<div class="flex items-center mb-6">
<Clock class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span>
{{ formatTime(batch.doc.start_time) }} -
{{ formatTime(batch.doc.end_time) }}
{{ formatTime(batch.data.start_time) }} -
{{ formatTime(batch.data.end_time) }}
</span>
</div>
<div v-html="batch.doc.description"></div>
<div v-html="batch.data.description"></div>
</div>
</div>
</div>
</div>
<div v-else class="h-screen">
<div class="text-base border rounded-md w-1/3 mx-auto my-32">
<div class="border-b px-5 py-3 font-medium">
<span
class="inline-flex items-center before:bg-red-600 before:w-2 before:h-2 before:rounded-md before:mr-2"
></span>
{{ __('Not Permitted') }}
</div>
<div class="px-5 py-3">
<div class="mb-4 leading-6">
{{
__(
'You are not a member of this batch. Please checkout our upcoming batches.'
)
}}
</div>
<router-link
:to="{
name: 'Batches',
params: {
batchName: batch.data?.name,
},
}"
>
<Button variant="solid" class="w-full">
{{ __('Upcoming Batches') }}
</Button>
</router-link>
</div>
</div>
</div>
</template>
<script setup>
import { Breadcrumbs, createDocumentResource } from 'frappe-ui'
import { computed, inject } from 'vue'
import { Calendar, Clock } from 'lucide-vue-next'
import { Breadcrumbs, Button, createResource, Tabs, Badge } from 'frappe-ui'
import { computed, inject, ref } from 'vue'
import { Calendar, Clock, LayoutDashboard, BookOpen } from 'lucide-vue-next'
import { formatTime } from '@/utils'
import CourseCard from '@/components/CourseCard.vue'
import BatchDashboard from '@/components/BatchDashboard.vue'
const dayjs = inject('$dayjs')
const user = inject('$user')
const props = defineProps({
batchName: {
@@ -47,10 +138,12 @@ const props = defineProps({
},
})
const batch = createDocumentResource({
doctype: 'LMS Batch',
name: props.batchName,
const batch = createResource({
url: 'lms.lms.utils.get_batch_details',
cache: ['batch', props.batchName],
params: {
batch: props.batchName,
},
auto: true,
})
@@ -62,9 +155,42 @@ const breadcrumbs = computed(() => {
route: { name: 'BatchDetail', params: { batchName: props.batchName } },
},
{
label: batch?.doc?.title,
label: batch?.data?.title,
route: { name: 'Batch', params: { batchName: props.batchName } },
},
]
})
const is_student = computed(() => {
return (
user?.data &&
batch.data?.students.length &&
batch.data?.students.includes(user.data.name)
)
})
const tabIndex = ref(0)
const tabs = []
if (is_student) {
tabs.push({
label: 'Dashboard',
icon: LayoutDashboard,
})
}
tabs.push({
label: 'Courses',
count: computed(() => courses?.data?.length),
icon: BookOpen,
})
const courses = createResource({
url: 'lms.lms.utils.get_batch_courses',
params: {
batch: props.batchName,
},
cache: ['batchCourses', props.batchName],
auto: true,
})
</script>

View File

@@ -1,42 +1,45 @@
<template>
<div v-if="batch.doc" class="h-screen text-base">
<div v-if="batch.data" class="h-screen text-base">
<header class="sticky top-0 z-10 border-b bg-white px-3 py-2.5 sm:px-5">
<Breadcrumbs :items="breadcrumbs" />
</header>
<div class="m-5 pb-10">
<div>
<div class="text-3xl font-semibold">
{{ batch.doc.title }}
{{ batch.data.title }}
</div>
<div class="my-3">
{{ batch.doc.description }}
{{ batch.data.description }}
</div>
<div class="flex items-center justify-between w-1/2">
<div class="flex items-center">
<BookOpen class="h-4 w-4 text-gray-700 mr-2" />
<span> {{ batch.doc.courses.length }} {{ __('Courses') }} </span>
<span> {{ batch.data.courses.length }} {{ __('Courses') }} </span>
</div>
<span v-if="batch.doc.courses">&middot;</span>
<span v-if="batch.data.courses">&middot;</span>
<div class="flex items-center">
<Calendar class="h-4 w-4 text-gray-700 mr-2" />
<span>
{{ dayjs(batch.doc.start_date).format('DD MMM YYYY') }} -
{{ dayjs(batch.doc.end_date).format('DD MMM YYYY') }}
{{ dayjs(batch.data.start_date).format('DD MMM YYYY') }} -
{{ dayjs(batch.data.end_date).format('DD MMM YYYY') }}
</span>
</div>
<span v-if="batch.doc.start_date">&middot;</span>
<span v-if="batch.data.start_date">&middot;</span>
<div class="flex items-center">
<Clock class="h-4 w-4 text-gray-700 mr-2" />
<span>
{{ formatTime(batch.doc.start_time) }} -
{{ formatTime(batch.doc.end_time) }}
{{ formatTime(batch.data.start_time) }} -
{{ formatTime(batch.data.end_time) }}
</span>
</div>
</div>
</div>
<div class="grid grid-cols-[60%,20%] gap-20 mt-10">
<div class="">
<div v-html="batch.doc.batch_details" class="batch-description"></div>
<div
v-html="batch.data.batch_details"
class="batch-description"
></div>
</div>
<div>
<BatchOverlay :batch="batch" />
@@ -46,29 +49,41 @@
<div class="text-2xl font-semibold">
{{ __('Courses') }}
</div>
<div
v-if="batch.doc.courses"
v-for="course in batch.doc.courses"
:key="course.course"
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5"
>
<router-link
:to="{
name: 'CourseDetail',
params: {
courseName: course.course,
},
}"
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5">
<div
v-if="batch.data.courses"
v-for="course in courses.data"
:key="course.course"
>
<CourseCard :course="course.course" :key="course.course" />
</router-link>
<router-link
:to="{
name: 'CourseDetail',
params: {
courseName: course.name,
},
}"
>
<CourseCard :course="course" :key="course.name" />
</router-link>
</div>
</div>
<div v-if="batch.data.batch_details_raw">
<div
v-html="batch.data.batch_details_raw"
class="batch-description"
></div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { Breadcrumbs, createDocumentResource } from 'frappe-ui'
import {
Breadcrumbs,
createDocumentResource,
createListResource,
createResource,
} from 'frappe-ui'
import { BookOpen, Calendar, Clock } from 'lucide-vue-next'
import { formatTime } from '../utils'
import { computed, inject } from 'vue'
@@ -84,18 +99,29 @@ const props = defineProps({
},
})
const batch = createDocumentResource({
doctype: 'LMS Batch',
name: props.batchName,
const batch = createResource({
url: 'lms.lms.utils.get_batch_details',
cache: ['batch', props.batchName],
params: {
batch: props.batchName,
},
auto: true,
})
const courses = createResource({
url: 'lms.lms.utils.get_batch_courses',
params: {
batch: props.batchName,
},
cache: ['batchCourses', props.batchName],
auto: true,
})
const breadcrumbs = computed(() => {
let items = [{ label: 'All Batches', route: { name: 'Batches' } }]
items.push({
label: batch?.doc?.title,
route: { name: 'BatchDetail', params: { batchName: batch?.doc?.name } },
label: batch?.data?.title,
route: { name: 'BatchDetail', params: { batchName: batch?.data?.name } },
})
return items
})

View File

@@ -1,119 +1,151 @@
<template>
<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>
<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>
</template>
</Tabs>
</div>
</div>
</div>
<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>
<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>
</template>
</Tabs>
</div>
</div>
</div>
</template>
<script setup>
import { sessionStore } from '@/stores/session'
import { createListResource, Breadcrumbs, Tabs, Badge, Button } from 'frappe-ui';
import CourseCard from '@/components/CourseCard.vue';
import { createListResource, Breadcrumbs, Tabs, Badge, Button } from 'frappe-ui'
import CourseCard from '@/components/CourseCard.vue'
import { Plus } from 'lucide-vue-next'
import { ref, computed, inject } from 'vue'
const user = inject("$user")
const user = inject('$user')
const courses = createListResource({
type: 'list',
doctype: 'LMS Course',
cache: ["courses", user?.data?.email],
url: "lms.lms.utils.get_courses",
auto: true,
});
type: 'list',
doctype: 'LMS Course',
cache: ['courses', user?.data?.email],
url: 'lms.lms.utils.get_courses',
auto: true,
})
const tabIndex = ref(0)
const tabs = [
{
label: 'Live',
courses: computed(() => courses.data?.live || []),
count: computed(() => courses.data?.live?.length),
},
{
label: 'Upcoming',
courses: computed(() => courses.data?.upcoming),
count: computed(() => courses.data?.upcoming?.length),
}
];
{
label: 'Live',
courses: computed(() => courses.data?.live || []),
count: computed(() => courses.data?.live?.length),
},
{
label: 'Upcoming',
courses: computed(() => courses.data?.upcoming),
count: computed(() => courses.data?.upcoming?.length),
},
]
if (user.data) {
tabs.push({
label: 'Enrolled',
courses: computed(() => courses.data?.enrolled),
count: computed(() => courses.data?.enrolled?.length),
});
tabs.push({
label: 'Enrolled',
courses: computed(() => courses.data?.enrolled),
count: computed(() => courses.data?.enrolled?.length),
})
if (user.data.is_moderator || user.data.is_instructor || courses.data?.created?.length) {
tabs.push({
label: 'Created',
courses: computed(() => courses.data?.created),
count: computed(() => courses.data?.created?.length),
});
};
if (
user.data.is_moderator ||
user.data.is_instructor ||
courses.data?.created?.length
) {
tabs.push({
label: 'Created',
courses: computed(() => courses.data?.created),
count: computed(() => courses.data?.created?.length),
})
}
if (user.data.is_moderator) {
tabs.push({
label: 'Under Review',
courses: computed(() => courses.data?.under_review),
count: computed(() => courses.data?.under_review?.length),
});
}
};
</script>
if (user.data.is_moderator) {
tabs.push({
label: 'Under Review',
courses: computed(() => courses.data?.under_review),
count: computed(() => courses.data?.under_review?.length),
})
}
}
</script>

View File

@@ -20,6 +20,15 @@ frappe.ui.form.on("LMS Batch", {
};
});
frm.set_query("assessment_type", "assessment", function () {
let doctypes = ["LMS Quiz", "LMS Assignment"];
return {
filters: {
name: ["in", doctypes],
},
};
});
frm.set_query("reference_doctype", "timetable_legends", function () {
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
return {

View File

@@ -297,7 +297,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-12-21 12:27:16.849362",
"modified": "2024-01-08 09:58:23.212334",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Batch",

View File

@@ -1348,12 +1348,27 @@ def get_neighbour_lesson(course, chapter, lesson):
@frappe.whitelist(allow_guest=True)
def get_batches():
batches = frappe.get_all(
batches = []
batch_list = frappe.get_all("LMS Batch", pluck="name")
for batch in batch_list:
batches.append(get_batch_details(batch))
batches = categorize_batches(batches)
return batches
@frappe.whitelist(allow_guest=True)
def get_batch_details(batch):
batch_details = frappe.db.get_value(
"LMS Batch",
fields=[
batch,
[
"name",
"title",
"description",
"batch_details",
"batch_details_raw",
"start_date",
"end_date",
"start_time",
@@ -1362,19 +1377,36 @@ def get_batches():
"published",
"amount",
"currency",
"paid_batch",
],
as_dict=True,
)
for batch in batches:
batch.courses = frappe.db.count("Batch Course", {"parent": batch.name})
batch.price = fmt_money(batch.amount, 0, batch.currency)
if batch.seat_count:
students_enrolled = frappe.db.count(
"Batch Student",
{"parent": batch.name},
batch_details.courses = frappe.get_all(
"Batch Course", {"parent": batch}, pluck="course"
)
batch_details.students = frappe.get_all(
"Batch Student", {"parent": batch}, pluck="student"
)
batch_details.price = fmt_money(batch_details.amount, 0, batch_details.currency)
is_student = frappe.session.user in batch_details.students
if frappe.session.user != "Guest":
if is_student:
batch_details.upcoming_evals = get_upcoming_evals(
frappe.session.user, batch_details.courses
)
batch.seats_left = batch.seat_count - students_enrolled
batches = categorize_batches(batches)
return batches
if is_student or has_course_moderator_role():
batch_details.assessments = get_assessments(batch, frappe.session.user)
if batch_details.seat_count:
students_enrolled = frappe.db.count(
"Batch Student",
{"parent": batch},
)
batch_details.seats_left = batch_details.seat_count - students_enrolled
return batch_details
def categorize_batches(batches):
@@ -1434,3 +1466,97 @@ def get_question_details(question):
question_details = frappe.db.get_value("LMS Question", question, fields, as_dict=1)
return question_details
@frappe.whitelist(allow_guest=True)
def get_batch_courses(batch):
courses = []
course_list = frappe.get_all("Batch Course", {"parent": batch}, pluck="course")
for course in course_list:
courses.append(get_course_details(course))
return courses
def get_assessments(batch, member=None):
if not member:
member = frappe.session.user
assessments = frappe.get_all(
"LMS Assessment",
{"parent": batch},
["name", "assessment_type", "assessment_name"],
)
for assessment in assessments:
if assessment.assessment_type == "LMS Assignment":
assessment = get_assignment_details(assessment, member)
elif assessment.assessment_type == "LMS Quiz":
assessment = get_quiz_details(assessment, member)
return assessments
def get_assignment_details(assessment, member):
assessment.title = frappe.db.get_value(
"LMS Assignment", assessment.assessment_name, "title"
)
existing_submission = frappe.db.exists(
{
"doctype": "LMS Assignment Submission",
"member": member,
"assignment": assessment.assessment_name,
}
)
assessment.completed = False
if existing_submission:
assessment.submission = frappe.db.get_value(
"LMS Assignment Submission",
existing_submission,
["name", "status", "comments"],
as_dict=True,
)
assessment.completed = True
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
submission_name = existing_submission if existing_submission else "new-submission"
assessment.url = (
f"/assignment-submission/{assessment.assessment_name}/{submission_name}"
)
return assessment
def get_quiz_details(assessment, member):
assessment_details = frappe.db.get_value(
"LMS Quiz", assessment.assessment_name, ["title", "passing_percentage"], as_dict=1
)
assessment.title = assessment_details.title
existing_submission = frappe.get_all(
"LMS Quiz Submission",
{
"member": member,
"quiz": assessment.assessment_name,
},
["name", "score", "percentage"],
order_by="percentage desc",
)
if len(existing_submission):
assessment.submission = existing_submission[0]
assessment.completed = False
if assessment.submission:
assessment.completed = True
assessment.edit_url = f"/quizzes/{assessment.assessment_name}"
submission_name = (
existing_submission[0].name if len(existing_submission) else "new-submission"
)
assessment.url = f"/quiz-submission/{assessment.assessment_name}/{submission_name}"
return assessment

View File

@@ -0,0 +1,730 @@
import {
a as k,
s as o,
u as n,
A as e,
E as a,
K as j,
L as A,
C as r,
D as s,
af as T,
k as M,
j as g,
r as E,
P as I,
z as h,
B as Y,
y as x,
J as P,
F as y,
X as f,
Z as F,
a0 as H,
a1 as O,
$ as S,
} from "./frappe-ui.f2211ca2.js";
import { f as $ } from "./index.05189aed.js";
import { _ as q } from "./CourseCard.6a41330a.js";
import { C as L, a as V } from "./clock.4d13ba48.js";
import { c as U, B as J } from "./index.43e529db.js";
import "./UserAvatar.b64a03ac.js";
import "./star.d3e8ecca.js";
const K = U("LayoutDashboardIcon", [
[
"rect",
{ width: "7", height: "9", x: "3", y: "3", rx: "1", key: "10lvy0" },
],
[
"rect",
{
width: "7",
height: "5",
x: "14",
y: "3",
rx: "1",
key: "16une8",
},
],
[
"rect",
{
width: "7",
height: "9",
x: "14",
y: "12",
rx: "1",
key: "1hutg5",
},
],
[
"rect",
{
width: "7",
height: "5",
x: "3",
y: "16",
rx: "1",
key: "ldoo1y",
},
],
]),
R = U("UserCog2Icon", [
["path", { d: "M14 19a6 6 0 0 0-12 0", key: "vej9p1" }],
["circle", { cx: "8", cy: "9", r: "4", key: "143rtg" }],
["circle", { cx: "19", cy: "11", r: "2", key: "1rxg02" }],
["path", { d: "M19 8v1", key: "1iffrw" }],
["path", { d: "M19 13v1", key: "z4xc62" }],
["path", { d: "m21.6 9.5-.87.5", key: "6lxupl" }],
["path", { d: "m17.27 12-.87.5", key: "1rwhxx" }],
["path", { d: "m21.6 12.5-.87-.5", key: "agvc9a" }],
["path", { d: "m17.27 10-.87-.5", key: "12d57s" }],
]),
X = { class: "mb-10" },
Z = { class: "text-lg font-semibold mb-4" },
G = { key: 0 },
Q = { class: "grid grid-cols-2" },
W = { class: "border rounded-md p-3" },
ee = { class: "font-medium mb-3" },
se = { class: "flex items-center mb-2" },
te = { class: "ml-2" },
ae = { class: "flex items-center mb-2" },
oe = { class: "ml-2" },
ne = { class: "flex items-center" },
ce = { class: "ml-2" },
re = { key: 1, class: "text-sm italic text-gray-600" },
le = {
__name: "UpcomingEvaluations",
props: { upcoming_evals: { type: Array, default: [] } },
setup(_) {
const l = k("$dayjs");
return (i, d) => (
o(),
n("div", X, [
e("div", Z, a(i.__("Upcoming Evaluations")), 1),
_.upcoming_evals.length
? (o(),
n("div", G, [
e("div", Q, [
(o(!0),
n(
j,
null,
A(
_.upcoming_evals,
(t) => (
o(),
n("div", null, [
e("div", W, [
e(
"div",
ee,
a(t.course_title),
1
),
e("div", se, [
r(s(L), {
class: "w-4 h-4 stroke-1.5",
}),
e(
"span",
te,
a(
s(l)(
t.date
).format(
"DD MMMM YYYY"
)
),
1
),
]),
e("div", ae, [
r(s(V), {
class: "w-4 h-4 stroke-1.5",
}),
e(
"span",
oe,
a(
s($)(
t.start_time
)
),
1
),
]),
e("div", ne, [
r(s(R), {
class: "w-4 h-4 stroke-1.5",
}),
e(
"span",
ce,
a(
t.evaluator_name
),
1
),
]),
]),
])
)
),
256
)),
]),
]))
: (o(),
n("div", re, a(i.__("No upcoming evaluations.")), 1)),
])
);
},
},
ie = { class: "text-lg font-semibold mb-4" },
de = { key: 0 },
me = { key: 1, class: "text-sm italic text-gray-600" },
_e = {
__name: "Assessments",
props: { assessments: { type: Array, default: [] } },
setup(_) {
return (l, i) => {
var d, t;
return (
o(),
n("div", null, [
e("div", ie, a(l.__("Assessments")), 1),
(d = _.assessments) != null && d.length
? (o(),
n("div", de, [
r(
s(T),
{
columns: l.getAssessmentColumns(),
rows:
(t = l.attempts) == null
? void 0
: t.data,
"row-key": "name",
options: {
selectable: !1,
showTooltip: !1,
},
},
null,
8,
["columns", "rows"]
),
]))
: (o(), n("div", me, a(l.__("No Assessments")), 1)),
])
);
};
},
},
ue = {
__name: "BatchDashboard",
props: { batch: { type: Object, default: null } },
setup(_) {
return (l, i) => (
o(),
n("div", null, [
r(
le,
{ upcoming_evals: _.batch.data.upcoming_evals },
null,
8,
["upcoming_evals"]
),
r(_e, { assessments: _.batch.data.assessments }, null, 8, [
"assessments",
]),
])
);
},
},
he = { key: 0, class: "h-screen text-base" },
pe = {
class: "sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5",
},
be = { key: 0 },
ye = { class: "grid grid-cols-[70%,30%] h-full" },
fe = { class: "border-r-2" },
ve = { class: "p-10" },
ge = { key: 0 },
xe = { class: "text-xl font-semibold" },
ke = { class: "grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 gap-8 mt-5" },
$e = { key: 1 },
we = { class: "p-5" },
Ne = { class: "text-2xl font-semibold mb-3" },
Ce = { class: "flex items-center mb-3" },
De = { class: "flex items-center mb-6" },
Be = ["innerHTML"],
Me = { key: 1, class: "h-screen" },
Ye = { class: "text-base border rounded-md w-1/3 mx-auto my-32" },
je = { class: "border-b px-5 py-3 font-medium" },
Ae = e(
"span",
{
class: "inline-flex items-center before:bg-red-600 before:w-2 before:h-2 before:rounded-md before:mr-2",
},
null,
-1
),
Le = { class: "px-5 py-3" },
Ve = { class: "mb-4 leading-6" },
He = {
__name: "Batch",
props: { batchName: { type: String, required: !0 } },
setup(_) {
const l = k("$dayjs"),
i = k("$user"),
d = _,
t = M({
url: "lms.lms.utils.get_batch_details",
cache: ["batch", d.batchName],
params: { batch: d.batchName },
auto: !0,
}),
z = g(() => {
var c;
return [
{ label: "All Batches", route: { name: "Batches" } },
{
label: "Batch Details",
route: {
name: "BatchDetail",
params: { batchName: d.batchName },
},
},
{
label:
(c = t == null ? void 0 : t.data) == null
? void 0
: c.title,
route: {
name: "Batch",
params: { batchName: d.batchName },
},
},
];
}),
w = g(() => {
var c, p;
return (
(i == null ? void 0 : i.data) &&
((c = t.data) == null ? void 0 : c.students.length) &&
((p = t.data) == null
? void 0
: p.students.includes(i.data.name))
);
}),
N = E(0),
v = [];
w && v.push({ label: "Dashboard", icon: K }),
v.push({
label: "Courses",
count: g(() => {
var c;
return (c = b == null ? void 0 : b.data) == null
? void 0
: c.length;
}),
icon: J,
});
const b = M({
url: "lms.lms.utils.get_batch_courses",
params: { batch: d.batchName },
cache: ["batchCourses", d.batchName],
auto: !0,
});
return (c, p) => {
var D, B;
const C = I("router-link");
return ((D = s(i).data) == null ? void 0 : D.is_moderator) ||
w.value
? (o(),
n("div", he, [
e("header", pe, [
r(
s(F),
{ class: "h-7", items: z.value },
null,
8,
["items"]
),
]),
s(t).data
? (o(),
n("div", be, [
e("div", ye, [
e("div", fe, [
r(
s(O),
{
class: "overflow-hidden",
modelValue: N.value,
"onUpdate:modelValue":
p[0] ||
(p[0] = (m) =>
(N.value = m)),
tabs: v,
},
{
tab: h(
({
tab: m,
selected: u,
}) => [
e("div", null, [
e(
"button",
{
class: Y(
[
"group -mb-px flex items-center gap-1 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",
{
"text-gray-900":
u,
},
]
),
},
[
m.icon
? (o(),
x(
P(
m.icon
),
{
key: 0,
class: "h-4 stroke-1.5",
}
))
: y(
"",
!0
),
f(
" " +
a(
c.__(
m.label
)
) +
" ",
1
),
m.count
? (o(),
x(
s(
H
),
{
key: 1,
class: Y(
{
"text-gray-900 border border-gray-900":
u,
}
),
variant:
"subtle",
theme: "gray",
size: "sm",
},
{
default:
h(
() => [
f(
a(
m.count
),
1
),
]
),
_: 2,
},
1032,
[
"class",
]
))
: y(
"",
!0
),
],
2
),
]),
]
),
default: h(
({ tab: m }) => [
e("div", ve, [
m.label ==
"Courses"
? (o(),
n(
"div",
ge,
[
e(
"div",
xe,
a(
c.__(
"Courses"
)
),
1
),
e(
"div",
ke,
[
(o(
!0
),
n(
j,
null,
A(
s(
b
)
.data,
(
u
) => (
o(),
n(
"div",
null,
[
r(
C,
{
to: {
name: "CourseDetail",
params: {
courseName:
u.name,
},
},
},
{
default:
h(
() => [
(o(),
x(
q,
{
key: u.name,
course: u,
},
null,
8,
[
"course",
]
)),
]
),
_: 2,
},
1032,
[
"to",
]
),
]
)
)
),
256
)),
]
),
]
))
: m.label ==
"Dashboard"
? (o(),
n(
"div",
$e,
[
r(
ue,
{
batch: s(
t
),
},
null,
8,
[
"batch",
]
),
]
))
: y(
"",
!0
),
]),
]
),
_: 1,
},
8,
["modelValue"]
),
]),
e("div", we, [
e(
"div",
Ne,
a(s(t).data.title),
1
),
e("div", Ce, [
r(s(L), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
e(
"span",
null,
a(
s(l)(
s(t).data
.start_date
).format(
"DD MMM YYYY"
)
) +
" - " +
a(
s(l)(
s(t).data
.end_date
).format(
"DD MMM YYYY"
)
),
1
),
]),
e("div", De, [
r(s(V), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
e(
"span",
null,
a(
s($)(
s(t).data
.start_time
)
) +
" - " +
a(
s($)(
s(t).data
.end_time
)
),
1
),
]),
e(
"div",
{
innerHTML:
s(t).data
.description,
},
null,
8,
Be
),
]),
]),
]))
: y("", !0),
]))
: (o(),
n("div", Me, [
e("div", Ye, [
e("div", je, [
Ae,
f(" " + a(c.__("Not Permitted")), 1),
]),
e("div", Le, [
e(
"div",
Ve,
a(
c.__(
"You are not a member of this batch. Please checkout our upcoming batches."
)
),
1
),
r(
C,
{
to: {
name: "Batches",
params: {
batchName:
(B = s(t).data) == null
? void 0
: B.name,
},
},
},
{
default: h(() => [
r(
s(S),
{
variant: "solid",
class: "w-full",
},
{
default: h(() => [
f(
a(
c.__(
"Upcoming Batches"
)
),
1
),
]),
_: 1,
}
),
]),
_: 1,
},
8,
["to"]
),
]),
]),
]));
};
},
};
export { He as default };

View File

@@ -0,0 +1,454 @@
import {
a as k,
j as D,
s as l,
u as m,
y as b,
z as f,
X as w,
E as s,
D as t,
a0 as M,
F as o,
A as a,
C as u,
$ as g,
k as Y,
P as N,
K as j,
L,
Z as T,
} from "./frappe-ui.f2211ca2.js";
import { a as H, f as p } from "./index.05189aed.js";
import { B as $ } from "./index.43e529db.js";
import { C as B, a as C } from "./clock.4d13ba48.js";
import { _ as O } from "./CourseCard.6a41330a.js";
import "./UserAvatar.b64a03ac.js";
import "./star.d3e8ecca.js";
const S = { key: 0, class: "shadow rounded-md p-5", style: { width: "300px" } },
V = { key: 2, class: "text-lg font-semibold mb-3" },
E = { class: "flex items-center mb-3" },
z = { class: "flex items-center mb-3" },
A = { class: "flex items-center" },
F = {
__name: "BatchOverlay",
props: { batch: { type: Object, default: null } },
setup(c) {
const y = k("$dayjs"),
_ = k("$user"),
e = c,
v = D(() => {
var r, d, i, n;
return (r = e.batch.data) != null && r.seat_count
? ((d = e.batch.data) == null ? void 0 : d.seat_count) -
((n =
(i = e.batch.data) == null
? void 0
: i.students) == null
? void 0
: n.length)
: null;
});
return (r, d) => {
var i, n, h, x;
return c.batch.data
? (l(),
m("div", S, [
c.batch.data.seat_count && v.value > 0
? (l(),
b(
t(M),
{
key: 0,
theme: "green",
class: "self-start mb-2 float-right",
},
{
default: f(() => [
w(
s(v.value) +
" " +
s(r.__("Seat Left")),
1
),
]),
_: 1,
}
))
: c.batch.data.seat_count && v.value <= 0
? (l(),
b(
t(M),
{
key: 1,
theme: "red",
class: "self-start mb-2 float-right",
},
{
default: f(() => [
w(s(r.__("Sold Out")), 1),
]),
_: 1,
}
))
: o("", !0),
c.batch.data.amount
? (l(),
m(
"div",
V,
s(
t(H)(
c.batch.data.amount,
c.batch.data.currency
)
),
1
))
: o("", !0),
a("div", E, [
u(t($), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
a(
"span",
null,
s(c.batch.data.courses.length) +
" " +
s(r.__("Courses")),
1
),
]),
a("div", z, [
u(t(B), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
a(
"span",
null,
s(
t(y)(c.batch.data.start_date).format(
"DD MMM YYYY"
)
) +
" - " +
s(
t(y)(c.batch.data.end_date).format(
"DD MMM YYYY"
)
),
1
),
]),
a("div", A, [
u(t(C), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
a(
"span",
null,
s(t(p)(c.batch.data.start_time)) +
" - " +
s(t(p)(c.batch.data.end_time)),
1
),
]),
(n = (i = t(_)) == null ? void 0 : i.data) !=
null && n.is_moderator
? (l(),
b(
t(g),
{ key: 3, class: "w-full mt-4" },
{
default: f(() => [
a(
"span",
null,
s(r.__("Manage Batch")),
1
),
]),
_: 1,
}
))
: c.batch.data.paid_batch
? (l(),
b(
t(g),
{
key: 4,
class: "w-full mt-4",
variant: "solid",
},
{
default: f(() => [
a(
"span",
null,
s(r.__("Register Now")),
1
),
]),
_: 1,
}
))
: o("", !0),
(x = (h = t(_)) == null ? void 0 : h.data) !=
null && x.is_moderator
? (l(),
b(
t(g),
{ key: 5, class: "w-full mt-2" },
{
default: f(() => [
a(
"span",
null,
s(r.__("Edit")),
1
),
]),
_: 1,
}
))
: o("", !0),
]))
: o("", !0);
};
},
};
const R = { key: 0, class: "h-screen text-base" },
q = { class: "sticky top-0 z-10 border-b bg-white px-3 py-2.5 sm:px-5" },
I = { class: "m-5 pb-10" },
K = { class: "text-3xl font-semibold" },
P = { class: "my-3" },
X = { class: "flex items-center justify-between w-1/2" },
Z = { class: "flex items-center" },
G = { key: 0 },
J = { class: "flex items-center" },
Q = { key: 1 },
U = { class: "flex items-center" },
W = { class: "grid grid-cols-[60%,20%] gap-20 mt-10" },
tt = { class: "" },
at = ["innerHTML"],
et = { class: "text-2xl font-semibold" },
st = { class: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5" },
ct = { key: 0 },
lt = ["innerHTML"],
_t = {
__name: "BatchDetail",
props: { batchName: { type: String, required: !0 } },
setup(c) {
const y = k("$dayjs"),
_ = c,
e = Y({
url: "lms.lms.utils.get_batch_details",
cache: ["batch", _.batchName],
params: { batch: _.batchName },
auto: !0,
}),
v = Y({
url: "lms.lms.utils.get_batch_courses",
params: { batch: _.batchName },
cache: ["batchCourses", _.batchName],
auto: !0,
}),
r = D(() => {
var i, n;
let d = [
{ label: "All Batches", route: { name: "Batches" } },
];
return (
d.push({
label:
(i = e == null ? void 0 : e.data) == null
? void 0
: i.title,
route: {
name: "BatchDetail",
params: {
batchName:
(n = e == null ? void 0 : e.data) ==
null
? void 0
: n.name,
},
},
}),
d
);
});
return (d, i) => {
const n = N("router-link");
return t(e).data
? (l(),
m("div", R, [
a("header", q, [
u(t(T), { items: r.value }, null, 8, ["items"]),
]),
a("div", I, [
a("div", null, [
a("div", K, s(t(e).data.title), 1),
a("div", P, s(t(e).data.description), 1),
a("div", X, [
a("div", Z, [
u(t($), {
class: "h-4 w-4 text-gray-700 mr-2",
}),
a(
"span",
null,
s(t(e).data.courses.length) +
" " +
s(d.__("Courses")),
1
),
]),
t(e).data.courses
? (l(), m("span", G, "\xB7"))
: o("", !0),
a("div", J, [
u(t(B), {
class: "h-4 w-4 text-gray-700 mr-2",
}),
a(
"span",
null,
s(
t(y)(
t(e).data.start_date
).format("DD MMM YYYY")
) +
" - " +
s(
t(y)(
t(e).data.end_date
).format("DD MMM YYYY")
),
1
),
]),
t(e).data.start_date
? (l(), m("span", Q, "\xB7"))
: o("", !0),
a("div", U, [
u(t(C), {
class: "h-4 w-4 text-gray-700 mr-2",
}),
a(
"span",
null,
s(t(p)(t(e).data.start_time)) +
" - " +
s(t(p)(t(e).data.end_time)),
1
),
]),
]),
]),
a("div", W, [
a("div", tt, [
a(
"div",
{
innerHTML:
t(e).data.batch_details,
class: "batch-description",
},
null,
8,
at
),
]),
a("div", null, [
u(F, { batch: t(e) }, null, 8, [
"batch",
]),
]),
]),
a("div", null, [
a("div", et, s(d.__("Courses")), 1),
a("div", st, [
t(e).data.courses
? (l(!0),
m(
j,
{ key: 0 },
L(
t(v).data,
(h) => (
l(),
m(
"div",
{
key: h.course,
},
[
u(
n,
{
to: {
name: "CourseDetail",
params: {
courseName:
h.name,
},
},
},
{
default:
f(
() => [
(l(),
b(
O,
{
course: h,
key: h.name,
},
null,
8,
[
"course",
]
)),
]
),
_: 2,
},
1032,
["to"]
),
]
)
)
),
128
))
: o("", !0),
]),
t(e).data.batch_details_raw
? (l(),
m("div", ct, [
a(
"div",
{
innerHTML:
t(e).data
.batch_details_raw,
class: "batch-description",
},
null,
8,
lt
),
]))
: o("", !0),
]),
]),
]))
: o("", !0);
};
},
};
export { _t as default };

View File

@@ -0,0 +1 @@
.batch-description p{margin-bottom:1rem;line-height:1.7}.batch-description li{line-height:1.7}.batch-description ol{list-style:auto;margin:revert;padding:revert}.batch-description strong{font-weight:600;color:#171717!important}

View File

@@ -0,0 +1 @@
.short-introduction{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-overflow:ellipsis;width:100%;overflow:hidden;margin:.25rem 0 1.25rem;line-height:1.5}

View File

@@ -0,0 +1,443 @@
import {
a as C,
s as n,
u,
y as g,
z as h,
X as _,
E as t,
D as c,
a0 as y,
F as x,
A as a,
C as i,
k as D,
r as Y,
j as m,
P as j,
B as $,
J as N,
K as V,
L as M,
Z as z,
$ as L,
a1 as P,
} from "./frappe-ui.f2211ca2.js";
import { f as B } from "./index.05189aed.js";
import { B as A } from "./index.43e529db.js";
import { C as E, a as O } from "./clock.4d13ba48.js";
import { P as S } from "./plus.8f4bce9f.js";
const F = {
class: "flex flex-col border border-gray-200 rounded-md p-4 h-full",
style: { "min-height": "150px" },
},
T = { class: "text-xl font-semibold mb-1" },
U = { class: "short-introduction" },
I = { class: "mt-auto" },
J = { key: 0, class: "font-semibold text-lg mb-4" },
K = { class: "flex items-center mb-3" },
R = { class: "flex items-center mb-3" },
X = { class: "flex items-center" },
Z = {
__name: "BatchCard",
props: { batch: { type: Object, default: null } },
setup(s) {
const d = C("$dayjs");
return (o, v) => (
n(),
u("div", F, [
s.batch.seat_count && s.batch.seats_left > 0
? (n(),
g(
c(y),
{
key: 0,
theme: "green",
class: "self-start mb-2",
},
{
default: h(() => [
_(
t(s.batch.seats_left) +
" " +
t(o.__("Seat Left")),
1
),
]),
_: 1,
}
))
: s.batch.seat_count && s.batch.seats_left <= 0
? (n(),
g(
c(y),
{
key: 1,
theme: "red",
class: "self-start mb-2",
},
{
default: h(() => [
_(t(o.__("Sold Out")), 1),
]),
_: 1,
}
))
: x("", !0),
a("div", T, t(s.batch.title), 1),
a("div", U, t(s.batch.description), 1),
a("div", I, [
s.batch.amount
? (n(), u("div", J, t(s.batch.price), 1))
: x("", !0),
a("div", K, [
i(c(A), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
a(
"span",
null,
t(s.batch.courses.length) +
" " +
t(o.__("Courses")),
1
),
]),
a("div", R, [
i(c(E), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
a(
"span",
null,
t(
c(d)(s.batch.start_date).format(
"DD MMM YYYY"
)
) +
" - " +
t(
c(d)(s.batch.end_date).format(
"DD MMM YYYY"
)
),
1
),
]),
a("div", X, [
i(c(O), {
class: "h-4 w-4 stroke-1.5 mr-2 text-gray-700",
}),
a(
"span",
null,
t(c(B)(s.batch.start_time)) +
" - " +
t(c(B)(s.batch.end_time)),
1
),
]),
]),
])
);
},
},
q = { class: "h-screen text-base" },
G = {
class: "sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5",
},
H = { class: "flex" },
Q = { class: "mx-5 py-5" },
W = {
key: 0,
class: "grid grid-cols-1 sm:grid-cols-3 md:grid-cols-4 gap-8 mt-5",
},
ee = {
key: 1,
class: "grid flex-1 place-items-center text-xl font-medium text-gray-500",
},
te = { class: "flex flex-col items-center justify-center mt-4" },
re = {
__name: "Batches",
setup(s) {
var p, k;
const d = C("$user"),
o = D({
url: "lms.lms.utils.get_batches",
cache: [
"batches",
(p = d == null ? void 0 : d.data) == null
? void 0
: p.email,
],
auto: !0,
}),
v = Y(0),
f = [
{
label: "Upcoming",
batches: m(() => {
var e;
return (
((e = o.data) == null ? void 0 : e.upcoming) ||
[]
);
}),
count: m(() => {
var e, l;
return (l =
(e = o.data) == null ? void 0 : e.upcoming) ==
null
? void 0
: l.length;
}),
},
];
return (
(k = d.data) != null &&
k.is_moderator &&
(f.push({
label: "Archived",
batches: m(() => {
var e;
return (e = o.data) == null ? void 0 : e.archived;
}),
count: m(() => {
var e, l;
return (l =
(e = o.data) == null ? void 0 : e.archived) ==
null
? void 0
: l.length;
}),
}),
f.push({
label: "Private",
batches: m(() => {
var e;
return (e = o.data) == null ? void 0 : e.private;
}),
count: m(() => {
var e, l;
return (l =
(e = o.data) == null ? void 0 : e.private) ==
null
? void 0
: l.length;
}),
})),
d.data &&
f.push({
label: "Enrolled",
batches: m(() => {
var e;
return (e = o.data) == null ? void 0 : e.enrolled;
}),
count: m(() => {
var e, l;
return (l =
(e = o.data) == null ? void 0 : e.enrolled) ==
null
? void 0
: l.length;
}),
}),
(e, l) => {
const w = j("router-link");
return (
n(),
u("div", q, [
a("header", G, [
i(
c(z),
{
class: "h-7",
items: [
{
label: e.__("All Batches"),
route: { name: "Batches" },
},
],
},
null,
8,
["items"]
),
a("div", H, [
i(
c(L),
{ variant: "solid" },
{
prefix: h(() => [
i(c(S), { class: "h-4 w-4" }),
]),
default: h(() => [
_(
" " + t(e.__("New Batch")),
1
),
]),
_: 1,
}
),
]),
]),
a("div", Q, [
i(
c(P),
{
class: "overflow-hidden",
modelValue: v.value,
"onUpdate:modelValue":
l[0] ||
(l[0] = (r) => (v.value = r)),
tabs: f,
},
{
tab: h(({ tab: r, selected: b }) => [
a("div", null, [
a(
"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",
{
"text-gray-900":
b,
},
]),
},
[
r.icon
? (n(),
g(N(r.icon), {
key: 0,
class: "h-5",
}))
: x("", !0),
_(
" " +
t(
e.__(
r.label
)
) +
" ",
1
),
i(
c(y),
{
class: $({
"text-gray-900 border border-gray-900":
b,
}),
variant:
"subtle",
theme: "gray",
size: "sm",
},
{
default: h(
() => [
_(
t(
r.count
),
1
),
]
),
_: 2,
},
1032,
["class"]
),
],
2
),
]),
]),
default: h(({ tab: r }) => [
r.batches && r.batches.value.length
? (n(),
u("div", W, [
(n(!0),
u(
V,
null,
M(
r.batches.value,
(b) => (
n(),
g(
w,
{
to: {
name: "BatchDetail",
params: {
batchName:
b.name,
},
},
},
{
default:
h(
() => [
i(
Z,
{
batch: b,
},
null,
8,
[
"batch",
]
),
]
),
_: 2,
},
1032,
["to"]
)
)
),
256
)),
]))
: (n(),
u("div", ee, [
a("div", te, [
a(
"div",
null,
t(
e
.__(
"No {0} batches found"
)
.format(
r.label.toLowerCase()
)
),
1
),
]),
])),
]),
_: 1,
},
8,
["modelValue"]
),
]),
])
);
}
);
},
};
export { re as default };

View File

@@ -1 +1 @@
.course-image{height:168px;width:100%;background-size:cover;background-position:center;background-repeat:no-repeat}.course-card-pills{background:#ffffff;margin-left:0;margin-right:.5rem;padding:3.5px 8px;font-size:11px;text-align:center;letter-spacing:.011em;text-transform:uppercase;font-weight:600;width:-moz-fit-content;width:fit-content}.default-image{display:flex;flex-direction:column;align-items:center;background-color:#ededed;color:#525252}
.course-image{height:168px;width:100%;background-size:cover;background-position:center;background-repeat:no-repeat}.course-card-pills{background:#ffffff;margin-left:0;margin-right:.5rem;padding:3.5px 8px;font-size:11px;text-align:center;letter-spacing:.011em;text-transform:uppercase;font-weight:600;width:-moz-fit-content;width:fit-content}.default-image{display:flex;flex-direction:column;align-items:center;background-color:#ededed;color:#525252}.avatar-group{display:inline-flex;align-items:center}.avatar-group .avatar{transition:margin .1s ease-in-out}.image-placeholder{display:flex;align-items:center;flex:1;font-size:5rem;color:#525252;font-weight:600}.avatar-group.overlap .avatar+.avatar{margin-left:-8px}.short-introduction{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-overflow:ellipsis;width:100%;overflow:hidden;margin:.25rem 0 1.25rem;line-height:1.5}

View File

@@ -0,0 +1,298 @@
import { _ as f } from "./UserAvatar.b64a03ac.js";
import { s as g, B as v, U as y } from "./index.43e529db.js";
import {
s,
u as r,
A as o,
K as i,
L as d,
E as t,
F as c,
B as m,
a2 as h,
C as n,
D as a,
z as x,
X as b,
a0 as k,
y as w,
} from "./frappe-ui.f2211ca2.js";
import { S as _ } from "./star.d3e8ecca.js";
const C = {
key: 0,
class: "flex flex-col border border-gray-200 h-full rounded-md shadow-sm text-base overflow-auto",
style: { "min-height": "320px" },
},
B = { class: "flex relative top-4 left-4 w-fit" },
S = { class: "course-card-pills rounded-md border border-gray-200" },
z = { key: 0, class: "image-placeholder" },
N = { class: "flex flex-col flex-auto p-4" },
U = { class: "flex items-center justify-between mb-2" },
V = { key: 0, class: "flex items-center space-x-1 py-1" },
j = { key: 1, class: "flex items-center space-x-1 py-1" },
A = { key: 2, class: "flex items-center space-x-1 py-1" },
D = { key: 3 },
E = { class: "text-xl font-semibold" },
F = { class: "short-introduction" },
I = { key: 0, class: "w-full bg-gray-200 rounded-full h-1 mb-2" },
L = { key: 1, class: "text-sm mb-4" },
M = { class: "flex items-center justify-between mt-auto" },
O = { class: "flex avatar-group overlap" },
R = { key: 0 },
$ = { key: 1 },
K = { key: 2 },
T = { class: "font-semibold" },
Q = {
__name: "CourseCard",
props: { course: { type: Object, default: null } },
setup(e) {
const { user: u } = g();
return (X, q) =>
e.course.title
? (s(),
r("div", C, [
o(
"div",
{
class: m([
"course-image",
{ "default-image": !e.course.image },
]),
style: h({
backgroundImage:
"url(" +
encodeURI(e.course.image) +
")",
}),
},
[
o("div", B, [
(s(!0),
r(
i,
null,
d(
e.course.tags,
(l) => (
s(), r("div", S, t(l), 1)
)
),
256
)),
]),
e.course.image
? c("", !0)
: (s(),
r("div", z, t(e.course.title[0]), 1)),
],
6
),
o("div", N, [
o("div", U, [
e.course.lesson_count
? (s(),
r("div", V, [
n(a(v), {
class: "h-4 w-4 stroke-1.5 text-gray-700",
}),
o(
"span",
null,
t(e.course.lesson_count),
1
),
]))
: c("", !0),
e.course.enrollment_count
? (s(),
r("div", j, [
n(a(y), {
class: "h-4 w-4 stroke-1.5 text-gray-700",
}),
o(
"span",
null,
t(
e.course
.enrollment_count
),
1
),
]))
: c("", !0),
e.course.avg_rating
? (s(),
r("div", A, [
n(a(_), {
class: "h-4 w-4 stroke-1.5 text-gray-700",
}),
o(
"span",
null,
t(e.course.avg_rating),
1
),
]))
: c("", !0),
e.course.status != "Approved"
? (s(),
r("div", D, [
n(
a(k),
{
variant: "solid",
theme:
e.course.status ===
"Under Review"
? "orange"
: "blue",
size: "sm",
},
{
default: x(() => [
b(
t(
e.course
.status
),
1
),
]),
_: 1,
},
8,
["theme"]
),
]))
: c("", !0),
]),
o("div", E, t(e.course.title), 1),
o("div", F, t(e.course.short_introduction), 1),
a(u) && e.course.membership
? (s(),
r("div", I, [
o(
"div",
{
class: "bg-gray-900 h-1 rounded-full",
style: h({
width:
Math.ceil(
e.course
.membership
.progress
) + "%",
}),
},
null,
4
),
]))
: c("", !0),
a(u) && e.course.membership
? (s(),
r(
"div",
L,
t(
Math.ceil(
e.course.membership.progress
)
) + "% completed ",
1
))
: c("", !0),
o("div", M, [
o("div", O, [
o(
"div",
{
class: m([
"mr-1",
{
"avatar-group overlap":
e.course.instructors
.length > 1,
},
]),
},
[
(s(!0),
r(
i,
null,
d(
e.course.instructors,
(l) => (
s(),
w(
f,
{ user: l },
null,
8,
["user"]
)
)
),
256
)),
],
2
),
e.course.instructors.length == 1
? (s(),
r(
"span",
R,
t(
e.course.instructors[0]
.full_name
),
1
))
: c("", !0),
e.course.instructors.length == 2
? (s(),
r(
"span",
$,
t(
e.course.instructors[0]
.first_name
) +
" and " +
t(
e.course
.instructors[1]
.first_name
),
1
))
: c("", !0),
e.course.instructors.length > 2
? (s(),
r(
"span",
K,
t(
e.course.instructors[0]
.first_name
) +
" and " +
t(
e.course.instructors
.length - 1
) +
" others ",
1
))
: c("", !0),
]),
o("div", T, t(e.course.price), 1),
]),
]),
]))
: c("", !0);
},
};
export { Q as _ };

View File

@@ -0,0 +1 @@
.course-description p{margin-bottom:1rem;line-height:1.7}.course-description li{line-height:1.7}.course-description ol{list-style:auto;margin:revert;padding:revert}.avatar-group{display:inline-flex;align-items:center}.avatar-group .avatar{transition:margin .1s ease-in-out}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
.outline-lesson:has(.router-link-active){background-color:#f3f3f3;padding:.5rem 0 .5rem 2rem}

View File

@@ -0,0 +1,277 @@
import {
a8 as C,
k as b,
P as N,
s as e,
u as o,
A as r,
K as d,
L as h,
y as n,
z as c,
C as l,
D as t,
a9 as w,
B as M,
E as _,
aa as V,
F as z,
X as B,
ab as I,
} from "./frappe-ui.f2211ca2.js";
import { c as i } from "./index.43e529db.js";
const L = i("ChevronRightIcon", [
["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }],
]),
F = i("FileTextIcon", [
[
"path",
{
d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z",
key: "1nnpy2",
},
],
["polyline", { points: "14 2 14 8 20 8", key: "1ew0cm" }],
["line", { x1: "16", x2: "8", y1: "13", y2: "13", key: "14keom" }],
["line", { x1: "16", x2: "8", y1: "17", y2: "17", key: "17nazh" }],
["line", { x1: "10", x2: "8", y1: "9", y2: "9", key: "1a5vjj" }],
]),
O = i("HelpCircleIcon", [
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", key: "1u773s" }],
["path", { d: "M12 17h.01", key: "p32p05" }],
]),
R = i("MonitorPlayIcon", [
["path", { d: "m10 7 5 3-5 3Z", key: "29ljg6" }],
[
"rect",
{
width: "20",
height: "14",
x: "2",
y: "3",
rx: "2",
key: "48i651",
},
],
["path", { d: "M12 17v4", key: "1riwvh" }],
["path", { d: "M8 21h8", key: "1ev6f3" }],
]);
const j = { class: "course-outline text-base" },
q = { class: "mt-4" },
H = { class: "text-base" },
P = { class: "outline-lesson mb-2 pl-9" },
T = { class: "flex items-center text-sm" },
X = {
__name: "CourseOutline",
props: { courseName: { type: String, required: !0 } },
setup(m) {
const k = C(),
y = m,
x = b({
url: "lms.lms.utils.get_course_outline",
cache: ["course_outline", y.courseName],
params: { course: y.courseName },
auto: !0,
}),
v = (u) => u == k.params.chapterNumber || u == 1;
return (u, D) => {
const f = N("router-link");
return (
e(),
o("div", j, [
r("div", q, [
(e(!0),
o(
d,
null,
h(
t(x).data,
(a, g) => (
e(),
n(
t(I),
{
key: a.name,
defaultOpen: v(a.idx),
},
{
default: c(({ open: p }) => [
l(
t(w),
{
class: "flex w-full px-2 pt-2 pb-3",
},
{
default: c(() => [
l(
t(L),
{
class: M(
[
{
"rotate-90 transform duration-200":
p,
"duration-200":
!p,
open:
g ==
1,
},
"h-5 w-5 text-gray-900 stroke-1 mr-2",
]
),
},
null,
8,
["class"]
),
r(
"div",
H,
_(a.title),
1
),
]),
_: 2,
},
1024
),
l(
t(V),
{ class: "pb-2" },
{
default: c(() => [
(e(!0),
o(
d,
null,
h(
a.lessons,
(s) => (
e(),
o(
"div",
{
key: s.name,
},
[
r(
"div",
P,
[
l(
f,
{
to: {
name: "Lesson",
params: {
courseName:
m.courseName,
chapterNumber:
s.number.split(
"."
)[0],
lessonNumber:
s.number.split(
"."
)[1],
},
},
},
{
default:
c(
() => [
r(
"div",
T,
[
s.icon ===
"icon-youtube"
? (e(),
n(
t(
R
),
{
key: 0,
class: "h-4 w-4 text-gray-900 stroke-1 mr-2",
}
))
: s.icon ===
"icon-quiz"
? (e(),
n(
t(
O
),
{
key: 1,
class: "h-4 w-4 text-gray-900 stroke-1 mr-2",
}
))
: s.icon ===
"icon-list"
? (e(),
n(
t(
F
),
{
key: 2,
class: "h-4 w-4 text-gray-900 stroke-1 mr-2",
}
))
: z(
"",
!0
),
B(
" " +
_(
s.title
),
1
),
]
),
]
),
_: 2,
},
1032,
[
"to",
]
),
]
),
]
)
)
),
128
)),
]),
_: 2,
},
1024
),
]),
_: 2,
},
1032,
["defaultOpen"]
)
)
),
128
)),
]),
])
);
};
},
};
export { L as C, X as _ };

View File

@@ -1,286 +0,0 @@
var N = Object.defineProperty,
S = Object.defineProperties;
var j = Object.getOwnPropertyDescriptors;
var h = Object.getOwnPropertySymbols;
var b = Object.prototype.hasOwnProperty,
w = Object.prototype.propertyIsEnumerable;
var p = (e, s, t) =>
s in e
? N(e, s, {
enumerable: !0,
configurable: !0,
writable: !0,
value: t,
})
: (e[s] = t),
m = (e, s) => {
for (var t in s || (s = {})) b.call(s, t) && p(e, t, s[t]);
if (h) for (var t of h(s)) w.call(s, t) && p(e, t, s[t]);
return e;
},
g = (e, s) => S(e, j(s));
var C = (e, s) => {
var t = {};
for (var a in e) b.call(e, a) && s.indexOf(a) < 0 && (t[a] = e[a]);
if (e != null && h)
for (var a of h(e)) s.indexOf(a) < 0 && w.call(e, a) && (t[a] = e[a]);
return t;
};
import {
l as $,
o,
d as c,
k as r,
F as v,
m as y,
t as l,
n as B,
p as z,
q as A,
e as n,
u,
v as O,
x as q,
y as U,
z as V,
} from "./frappe-ui.8966d601.js";
var _ = {
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": 2,
"stroke-linecap": "round",
"stroke-linejoin": "round",
};
const F = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(),
f =
(e, s) =>
(oe, { attrs: d, slots: x }) => {
var k = oe,
{
size: t,
strokeWidth: a = 2,
absoluteStrokeWidth: i,
color: M,
} = k,
I = C(k, [
"size",
"strokeWidth",
"absoluteStrokeWidth",
"color",
]);
return $(
"svg",
m(
g(
m(
g(m({}, _), {
width: t || _.width,
height: t || _.height,
stroke: M || _.stroke,
"stroke-width": i
? (Number(a) * 24) / Number(t)
: a,
}),
d
),
{
class: [
"lucide",
`lucide-${F(e)}`,
(d == null ? void 0 : d.class) || "",
],
}
),
I
),
[...s.map((L) => $(...L)), ...(x.default ? [x.default()] : [])]
);
},
H = f("BookOpenIcon", [
[
"path",
{ d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z", key: "vv98re" },
],
[
"path",
{ d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z", key: "1cyq3y" },
],
]),
D = f("StarIcon", [
[
"polygon",
{
points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2",
key: "8f66p6",
},
],
]),
E = f("UsersIcon", [
[
"path",
{ d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", key: "1yyitq" },
],
["circle", { cx: "9", cy: "7", r: "4", key: "nufk8" }],
["path", { d: "M22 21v-2a4 4 0 0 0-3-3.87", key: "kshegd" }],
["path", { d: "M16 3.13a4 4 0 0 1 0 7.75", key: "1da9ce" }],
]);
const K = {
class: "flex flex-col h-full border border-gray-200 rounded-md shadow-sm mt-5",
},
P = { class: "flex relative top-4 left-4" },
R = { class: "course-card-pills rounded-md border border-gray-200" },
Z = { key: 0, class: "flex flex-1 text-4xl font-bold" },
G = { class: "p-4" },
J = { class: "flex text-base items-center justify-between" },
Q = { class: "flex items-center space-x-1 py-1" },
T = { class: "flex items-center space-x-1 py-1" },
X = { class: "flex items-center space-x-1 py-1" },
Y = { class: "text-2xl font-semibold" },
W = { class: "text-ellipsis truncate text-base" },
ee = {
__name: "CourseCard",
props: { course: { type: Object, default: null } },
setup(e) {
return (s, t) => (
o(),
c("div", K, [
r(
"div",
{
class: z([
"course-image",
{ "default-image": !e.course.image },
]),
style: A({
backgroundImage: "url(" + e.course.image + ")",
}),
},
[
r("div", P, [
(o(!0),
c(
v,
null,
y(
e.course.tags,
(a) => (o(), c("div", R, l(a), 1))
),
256
)),
]),
e.course.image
? B("", !0)
: (o(), c("div", Z, l(e.course.title[0]), 1)),
],
6
),
r("div", G, [
r("div", J, [
r("div", Q, [
n(u(H), { class: "h-4 w-4 text-gray-700" }),
r("span", null, l(e.course.lesson_count), 1),
]),
r("div", T, [
n(u(E), { class: "h-4 w-4 text-gray-700" }),
r(
"span",
null,
l(e.course.enrollment_count),
1
),
]),
r("div", X, [
n(u(D), { class: "h-4 w-4 text-gray-700" }),
r("span", null, l(e.course.avg_rating), 1),
]),
]),
r("div", Y, l(e.course.title), 1),
r("div", W, l(e.course.short_introduction), 1),
(o(!0),
c(
v,
null,
y(e.course.instructors, (a) => (o(), c("div"))),
256
)),
]),
])
);
},
},
se = {
__name: "UserAvatar",
props: { user: { type: Object, default: null } },
setup(e) {
return (s, t) =>
e.user
? (o(),
O(
u(U),
q(
{
key: 0,
class: "",
label: e.user.full_name,
image: e.user.user_image,
},
s.$attrs
),
null,
16,
["label", "image"]
))
: B("", !0);
},
},
te = { class: "container" },
ae = r("div", { class: "text-2xl font-semibold" }, " All Courses ", -1),
re = { class: "grid grid-cols-3 gap-8" },
ne = {
__name: "Courses",
setup(e) {
const s = V({
type: "list",
doctype: "LMS Course",
url: "lms.lms.utils.get_courses",
auto: !0,
});
return (t, a) => (
o(),
c("div", te, [
ae,
r("div", re, [
(o(!0),
c(
v,
null,
y(
u(s).data,
(i) => (
o(),
c("div", null, [
n(ee, { course: i }, null, 8, [
"course",
]),
n(
se,
{ user: i.instructors[0] },
null,
8,
["user"]
),
])
)
),
256
)),
]),
])
);
},
};
export { ne as default };

View File

@@ -0,0 +1,433 @@
import {
a as k,
Y as w,
r as L,
j as o,
P as V,
s as n,
u as m,
D as c,
A as i,
C as d,
z as u,
X as h,
E as p,
B as y,
y as x,
J as $,
F as C,
K as B,
L as j,
Z as z,
$ as D,
a0 as E,
a1 as P,
} from "./frappe-ui.f2211ca2.js";
import { _ as U } from "./CourseCard.6a41330a.js";
import { P as A } from "./plus.8f4bce9f.js";
import "./UserAvatar.b64a03ac.js";
import "./index.43e529db.js";
import "./star.d3e8ecca.js";
const F = { class: "h-screen" },
R = { key: 0 },
S = {
class: "sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5",
},
I = { class: "flex" },
J = { class: "mx-5 py-5" },
K = {
key: 0,
class: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5",
},
M = {
key: 1,
class: "grid flex-1 place-items-center text-xl font-medium text-gray-500",
},
T = { class: "flex flex-col items-center justify-center mt-4" },
Q = {
__name: "Courses",
setup(X) {
var g, b, v;
const l = k("$user"),
a = w({
type: "list",
doctype: "LMS Course",
cache: [
"courses",
(g = l == null ? void 0 : l.data) == null
? void 0
: g.email,
],
url: "lms.lms.utils.get_courses",
auto: !0,
}),
f = L(0),
_ = [
{
label: "Live",
courses: o(() => {
var e;
return (
((e = a.data) == null ? void 0 : e.live) || []
);
}),
count: o(() => {
var e, s;
return (s =
(e = a.data) == null ? void 0 : e.live) == null
? void 0
: s.length;
}),
},
{
label: "Upcoming",
courses: o(() => {
var e;
return (e = a.data) == null ? void 0 : e.upcoming;
}),
count: o(() => {
var e, s;
return (s =
(e = a.data) == null ? void 0 : e.upcoming) ==
null
? void 0
: s.length;
}),
},
];
return (
l.data &&
(_.push({
label: "Enrolled",
courses: o(() => {
var e;
return (e = a.data) == null ? void 0 : e.enrolled;
}),
count: o(() => {
var e, s;
return (s =
(e = a.data) == null ? void 0 : e.enrolled) ==
null
? void 0
: s.length;
}),
}),
(l.data.is_moderator ||
l.data.is_instructor ||
((v = (b = a.data) == null ? void 0 : b.created) == null
? void 0
: v.length)) &&
_.push({
label: "Created",
courses: o(() => {
var e;
return (e = a.data) == null
? void 0
: e.created;
}),
count: o(() => {
var e, s;
return (s =
(e = a.data) == null
? void 0
: e.created) == null
? void 0
: s.length;
}),
}),
l.data.is_moderator &&
_.push({
label: "Under Review",
courses: o(() => {
var e;
return (e = a.data) == null
? void 0
: e.under_review;
}),
count: o(() => {
var e, s;
return (s =
(e = a.data) == null
? void 0
: e.under_review) == null
? void 0
: s.length;
}),
})),
(e, s) => {
const N = V("router-link");
return (
n(),
m("div", F, [
c(a).data
? (n(),
m("div", R, [
i("header", S, [
d(
c(z),
{
class: "h-7",
items: [
{
label: e.__(
"All Courses"
),
route: {
name: "Courses",
},
},
],
},
null,
8,
["items"]
),
i("div", I, [
d(
c(D),
{ variant: "solid" },
{
prefix: u(() => [
d(c(A), {
class: "h-4 w-4",
}),
]),
default: u(() => [
h(
" " +
p(
e.__(
"New Course"
)
),
1
),
]),
_: 1,
}
),
]),
]),
i("div", J, [
d(
c(P),
{
class: "overflow-hidden",
modelValue: f.value,
"onUpdate:modelValue":
s[0] ||
(s[0] = (r) =>
(f.value = r)),
tabs: _,
},
{
tab: u(
({
tab: r,
selected: t,
}) => [
i("div", null, [
i(
"button",
{
class: y(
[
"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",
{
"text-gray-900":
t,
},
]
),
},
[
r.icon
? (n(),
x(
$(
r.icon
),
{
key: 0,
class: "h-5",
}
))
: C(
"",
!0
),
h(
" " +
p(
e.__(
r.label
)
) +
" ",
1
),
d(
c(
E
),
{
class: y(
{
"text-gray-900 border border-gray-900":
t,
}
),
variant:
"subtle",
theme: "gray",
size: "sm",
},
{
default:
u(
() => [
h(
p(
r.count
),
1
),
]
),
_: 2,
},
1032,
[
"class",
]
),
],
2
),
]),
]
),
default: u(({ tab: r }) => [
r.courses &&
r.courses.value.length
? (n(),
m("div", K, [
(n(!0),
m(
B,
null,
j(
r
.courses
.value,
(
t
) => (
n(),
x(
N,
{
to:
t.membership &&
t.current_lesson
? {
name: "Lesson",
params: {
courseName:
t.name,
chapterNumber:
t.current_lesson.split(
"."
)[0],
lessonNumber:
t.current_lesson.split(
"."
)[1],
},
}
: t.membership
? {
name: "Lesson",
params: {
courseName:
t.name,
chapterNumber: 1,
lessonNumber: 1,
},
}
: {
name: "CourseDetail",
params: {
courseName:
t.name,
},
},
},
{
default:
u(
() => [
d(
U,
{
course: t,
},
null,
8,
[
"course",
]
),
]
),
_: 2,
},
1032,
[
"to",
]
)
)
),
256
)),
]))
: (n(),
m("div", M, [
i(
"div",
T,
[
i(
"div",
null,
p(
e
.__(
"No {0} courses found"
)
.format(
r.label.toLowerCase()
)
),
1
),
]
),
])),
]),
_: 1,
},
8,
["modelValue"]
),
]),
]))
: C("", !0),
])
);
}
);
},
};
export { Q as default };

View File

@@ -1,21 +1,21 @@
import {
b as f,
P as g,
q as f,
ai as g,
T as _,
r as d,
o,
v as l,
w as r,
A as p,
B as C,
C as k,
k as a,
d as c,
F as u,
m,
q as h,
p as b,
} from "./frappe-ui.8966d601.js";
P as c,
s as o,
y as l,
z as r,
I as p,
aj as C,
ak as k,
A as a,
u as d,
K as u,
L as m,
a2 as h,
B as b,
} from "./frappe-ui.f2211ca2.js";
const v = {
name: "FontColor",
props: ["editor"],
@@ -67,17 +67,17 @@ const v = {
B = a("div", { class: "text-sm text-gray-700" }, "Text Color", -1),
P = { class: "mt-1 grid grid-cols-8 gap-1" },
F = ["aria-label", "onClick"],
w = a(
D = a(
"div",
{ class: "mt-2 text-sm text-gray-700" },
"Background Color",
-1
),
D = { class: "mt-1 grid grid-cols-8 gap-1" },
w = { class: "mt-1 grid grid-cols-8 gap-1" },
T = ["aria-label", "onClick"];
function A(t, z, E, R, $, n) {
const i = d("Tooltip"),
x = d("Popover");
function z(t, A, j, E, R, n) {
const i = c("Tooltip"),
x = c("Popover");
return (
o(),
l(
@@ -96,7 +96,7 @@ function A(t, z, E, R, $, n) {
B,
a("div", P, [
(o(!0),
c(
d(
u,
null,
m(
@@ -141,10 +141,10 @@ function A(t, z, E, R, $, n) {
128
)),
]),
w,
a("div", D, [
D,
a("div", w, [
(o(!0),
c(
d(
u,
null,
m(
@@ -202,5 +202,5 @@ function A(t, z, E, R, $, n) {
)
);
}
const G = f(v, [["render", A]]);
const G = f(v, [["render", z]]);
export { G as default };

View File

@@ -1,15 +1,15 @@
import {
b as d,
D as g,
r,
o as m,
d as f,
e as t,
w as s,
j as l,
k as p,
t as u,
} from "./frappe-ui.8966d601.js";
q as d,
O as g,
P as r,
s as m,
u as f,
C as s,
z as t,
X as l,
A as u,
E as p,
} from "./frappe-ui.f2211ca2.js";
const D = {
name: "Home",
data() {
@@ -19,13 +19,13 @@ const D = {
components: { Dialog: g },
},
_ = { class: "max-w-3xl py-12 mx-auto" };
function k(e, o, w, C, n, V) {
function C(e, o, k, w, n, V) {
const a = r("Button"),
c = r("Dialog");
return (
m(),
f("div", _, [
t(
s(
a,
{
"icon-left": "code",
@@ -33,20 +33,20 @@ function k(e, o, w, C, n, V) {
loading: e.$resources.ping.loading,
},
{
default: s(() => [l(" Click to send 'ping' request ")]),
default: t(() => [l(" Click to send 'ping' request ")]),
_: 1,
},
8,
["onClick", "loading"]
),
p("div", null, u(e.$resources.ping.data), 1),
p("pre", null, u(e.$resources.ping), 1),
t(
u("div", null, p(e.$resources.ping.data), 1),
u("pre", null, p(e.$resources.ping), 1),
s(
a,
{ onClick: o[0] || (o[0] = (i) => (n.showDialog = !0)) },
{ default: s(() => [l("Open Dialog")]), _: 1 }
{ default: t(() => [l("Open Dialog")]), _: 1 }
),
t(
s(
c,
{
title: "Title",
@@ -54,12 +54,12 @@ function k(e, o, w, C, n, V) {
"onUpdate:modelValue":
o[1] || (o[1] = (i) => (n.showDialog = i)),
},
{ default: s(() => [l(" Dialog content ")]), _: 1 },
{ default: t(() => [l(" Dialog content ")]), _: 1 },
8,
["modelValue"]
),
])
);
}
const B = d(D, [["render", k]]);
const B = d(D, [["render", C]]);
export { B as default };

View File

@@ -1,30 +1,30 @@
import {
b as f,
h as I,
D,
G as h,
r as d,
o as m,
d as c,
A as _,
B as y,
C,
e as n,
w as s,
k as r,
t as w,
n as b,
j as u,
F as k,
} from "./frappe-ui.8966d601.js";
const v = {
q as I,
$ as f,
O as D,
an as h,
P as d,
s as m,
u as c,
I as _,
aj as y,
ak as C,
C as n,
z as s,
A as i,
E as k,
F as v,
X as u,
K as w,
} from "./frappe-ui.f2211ca2.js";
const b = {
name: "InsertImage",
props: ["editor"],
expose: ["openDialog"],
data() {
return { addImageDialog: { url: "", file: null, show: !1 } };
},
components: { Button: I, Dialog: D },
components: { Button: f, Dialog: D },
methods: {
openDialog() {
this.addImageDialog.show = !0;
@@ -33,8 +33,8 @@ const v = {
let e = t.target.files[0];
!e ||
((this.addImageDialog.file = e),
h(e).then((i) => {
this.addImageDialog.url = i;
h(e).then((r) => {
this.addImageDialog.url = r;
}));
},
addImage(t) {
@@ -46,18 +46,18 @@ const v = {
},
},
},
B = {
x = {
class: "relative cursor-pointer rounded-lg bg-gray-100 py-1 focus-within:bg-gray-200 hover:bg-gray-200",
},
x = { class: "absolute inset-0 select-none px-2 py-1 text-base" },
B = { class: "absolute inset-0 select-none px-2 py-1 text-base" },
S = ["src"];
function V(t, e, i, A, a, o) {
function V(t, e, r, A, a, o) {
const g = d("Button"),
p = d("Dialog");
return (
m(),
c(
k,
w,
null,
[
_(t.$slots, "default", y(C({ onClick: o.openDialog }))),
@@ -72,8 +72,8 @@ function V(t, e, i, A, a, o) {
},
{
"body-content": s(() => [
r("label", B, [
r(
i("label", x, [
i(
"input",
{
type: "file",
@@ -88,10 +88,10 @@ function V(t, e, i, A, a, o) {
null,
32
),
r(
i(
"span",
x,
w(
B,
k(
a.addImageDialog.file
? "Select another image"
: "Select an image"
@@ -112,7 +112,7 @@ function V(t, e, i, A, a, o) {
8,
S
))
: b("", !0),
: v("", !0),
]),
actions: s(() => [
n(
@@ -147,5 +147,5 @@ function V(t, e, i, A, a, o) {
)
);
}
const F = f(v, [["render", V]]);
export { F as default };
const P = I(b, [["render", V]]);
export { P as default };

View File

@@ -1,20 +1,20 @@
import {
b as d,
h as g,
I as L,
D as m,
r as i,
o as p,
d as f,
A as D,
B as h,
C as c,
e as l,
w as a,
E as w,
j as _,
F as v,
} from "./frappe-ui.8966d601.js";
q as d,
$ as g,
al as L,
O as m,
P as i,
s as p,
u as f,
I as D,
aj as c,
ak as h,
C as l,
z as a,
am as _,
X as v,
K as w,
} from "./frappe-ui.f2211ca2.js";
const x = {
name: "InsertLink",
props: ["editor"],
@@ -56,10 +56,10 @@ function V(t, e, C, B, n, s) {
return (
p(),
f(
v,
w,
null,
[
D(t.$slots, "default", h(c({ onClick: s.openDialog }))),
D(t.$slots, "default", c(h({ onClick: s.openDialog }))),
l(
k,
{
@@ -83,7 +83,7 @@ function V(t, e, C, B, n, s) {
(n.setLinkDialog.url = o)),
onKeydown:
e[1] ||
(e[1] = w(
(e[1] = _(
(o) => s.setLink(o.target.value),
["enter"]
)),
@@ -103,7 +103,7 @@ function V(t, e, C, B, n, s) {
(e[2] = (o) =>
s.setLink(n.setLinkDialog.url)),
},
{ default: a(() => [_(" Save ")]), _: 1 }
{ default: a(() => [v(" Save ")]), _: 1 }
),
]),
_: 1,
@@ -116,5 +116,5 @@ function V(t, e, C, B, n, s) {
)
);
}
const b = d(x, [["render", V]]);
export { b as default };
const y = d(x, [["render", V]]);
export { y as default };

View File

@@ -1,24 +1,24 @@
import {
b as _,
h as C,
D as k,
H as v,
r,
o as u,
d as c,
A as h,
B,
C as w,
e as t,
w as l,
k as x,
j as n,
t as y,
v as U,
n as p,
F,
} from "./frappe-ui.8966d601.js";
const A = {
q as _,
$ as C,
O as k,
ao as v,
P as r,
s as u,
u as c,
I as h,
aj as x,
ak as y,
C as a,
z as l,
A as B,
X as n,
E as w,
y as U,
F as p,
K as F,
} from "./frappe-ui.f2211ca2.js";
const I = {
name: "InsertImage",
props: ["editor"],
expose: ["openDialog"],
@@ -47,9 +47,9 @@ const A = {
},
},
},
I = { class: "flex items-center space-x-2" },
A = { class: "flex items-center space-x-2" },
N = ["src"];
function S(i, o, b, L, e, a) {
function S(i, o, P, z, e, t) {
const s = r("Button"),
V = r("FileUploader"),
g = r("Dialog");
@@ -59,19 +59,19 @@ function S(i, o, b, L, e, a) {
F,
null,
[
h(i.$slots, "default", B(w({ onClick: a.openDialog }))),
t(
h(i.$slots, "default", x(y({ onClick: t.openDialog }))),
a(
g,
{
options: { title: "Add Video" },
modelValue: e.addVideoDialog.show,
"onUpdate:modelValue":
o[2] || (o[2] = (d) => (e.addVideoDialog.show = d)),
onAfterLeave: a.reset,
onAfterLeave: t.reset,
},
{
"body-content": l(() => [
t(
a(
V,
{
"file-types": "video/*",
@@ -89,14 +89,14 @@ function S(i, o, b, L, e, a) {
uploading: m,
openFileSelector: D,
}) => [
x("div", I, [
t(
B("div", A, [
a(
s,
{ onClick: D },
{
default: l(() => [
n(
y(
w(
m
? `Uploading ${f}%`
: e
@@ -164,23 +164,23 @@ function S(i, o, b, L, e, a) {
: p("", !0),
]),
actions: l(() => [
t(
a(
s,
{
variant: "solid",
onClick:
o[1] ||
(o[1] = (d) =>
a.addVideo(e.addVideoDialog.url)),
t.addVideo(e.addVideoDialog.url)),
},
{
default: l(() => [n(" Insert Video ")]),
_: 1,
}
),
t(
a(
s,
{ onClick: a.reset },
{ onClick: t.reset },
{ default: l(() => [n("Cancel")]), _: 1 },
8,
["onClick"]
@@ -196,5 +196,5 @@ function S(i, o, b, L, e, a) {
)
);
}
const R = _(A, [["render", S]]);
export { R as default };
const L = _(I, [["render", S]]);
export { L as default };

View File

@@ -0,0 +1 @@
.avatar-group{display:inline-flex;align-items:center}.avatar-group .avatar{transition:margin .1s ease-in-out}iframe{border:1px solid #ddd;border-radius:.5rem;margin-bottom:1rem}.lesson-content p{margin-bottom:1rem;line-height:1.7}.lesson-content li{line-height:1.7}.lesson-content ol{list-style:auto;margin:revert;padding:1rem}.lesson-content ul{list-style:auto;padding:1rem;margin:revert}.lesson-content img{border:1px solid #EDEDED;border-radius:.5rem}.lesson-content code{display:block;overflow-x:auto;padding:1rem 1.25rem;background:#011627;color:#d6deeb;border-radius:.5rem;margin-bottom:1rem}.lesson-content a{color:#171717;text-decoration:underline;font-weight:500}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
import {
s as r,
y as s,
a3 as t,
D as l,
a4 as u,
F as n,
} from "./frappe-ui.f2211ca2.js";
const i = {
__name: "UserAvatar",
props: { user: { type: Object, default: null }, size: { type: String } },
setup(e) {
return (a, m) =>
e.user
? (r(),
s(
l(u),
t(
{
key: 0,
class: "avatar border border-gray-300",
label: e.user.full_name,
image: e.user.user_image,
size: e.size,
},
a.$attrs
),
null,
16,
["label", "image", "size"]
))
: n("", !0);
},
};
export { i as _ };

View File

@@ -0,0 +1,23 @@
import { c as e } from "./index.43e529db.js";
const c = e("CalendarIcon", [
[
"rect",
{
width: "18",
height: "18",
x: "3",
y: "4",
rx: "2",
ry: "2",
key: "eu3xkr",
},
],
["line", { x1: "16", x2: "16", y1: "2", y2: "6", key: "m3sa8f" }],
["line", { x1: "8", x2: "8", y1: "2", y2: "6", key: "18kwsl" }],
["line", { x1: "3", x2: "21", y1: "10", y2: "10", key: "xt86sb" }],
]),
l = e("ClockIcon", [
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
["polyline", { points: "12 6 12 12 16 14", key: "68esgv" }],
]);
export { c as C, l as a };

View File

@@ -1 +1 @@
.form-select{background-image:url("data:image/svg+xml;utf8,<svg fill='none' width='8' xmlns='http://www.w3.org/2000/svg' viewBox='-4 -2 16 16'><path d='M4.5 3.636 6.136 2l1.637 1.636M4.5 8.364 6.136 10l1.637-1.636' stroke='%23333C44' stroke-linecap='round' stroke-linejoin='round'/></svg>")}.spinner[data-v-10b50c78]{animation:rotate-10b50c78 2s linear infinite}.spinner-path[data-v-10b50c78]{stroke-linecap:round;animation:dash-10b50c78 1.5s ease-in-out infinite}@keyframes rotate-10b50c78{to{transform:rotate(360deg)}}@keyframes dash-10b50c78{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:120,150;stroke-dashoffset:-124}}.item{display:block;margin:0;width:100%;text-align:left;background:transparent;border-radius:.4rem;border:1px solid transparent;padding:.2rem .4rem}.item.is-selected{border-color:#000}.ProseMirror{outline:none;caret-color:#171717;word-break:break-word}.ProseMirror-focused:focus-visible{outline:none}.ProseMirror:not(.ProseMirror-focused) p.is-editor-empty:first-child:before{content:attr(data-placeholder);float:left;color:#999;pointer-events:none;height:0}.ProseMirror-selectednode video,img.ProseMirror-selectednode{outline:2px solid #E2E2E2}.mention{font-weight:600;-webkit-box-decoration-break:clone;box-decoration-break:clone}.prose table p{margin:0}.ProseMirror table .selectedCell:after{z-index:2;position:absolute;content:"";inset:0;pointer-events:none;background:#E3F1FD;opacity:.3}.ProseMirror table .column-resize-handle{position:absolute;right:-1px;top:0;bottom:-2px;width:4px;background-color:#e3f1fd;pointer-events:none}.resize-cursor{cursor:ew-resize;cursor:col-resize}.ProseMirror mark{border-radius:3px;padding:0 2px}
.form-select{background-image:url("data:image/svg+xml;utf8,<svg fill='none' width='8' xmlns='http://www.w3.org/2000/svg' viewBox='-4 -2 16 16'><path d='M4.5 3.636 6.136 2l1.637 1.636M4.5 8.364 6.136 10l1.637-1.636' stroke='%23333C44' stroke-linecap='round' stroke-linejoin='round'/></svg>")}.spinner[data-v-d1174afc]{animation:rotate-d1174afc 2s linear infinite}.spinner-path[data-v-d1174afc]{stroke-linecap:round;animation:dash-d1174afc 1.5s ease-in-out infinite}@keyframes rotate-d1174afc{to{transform:rotate(360deg)}}@keyframes dash-d1174afc{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:120,150;stroke-dashoffset:-124}}.item{display:block;margin:0;width:100%;text-align:left;background:transparent;border-radius:.4rem;border:1px solid transparent;padding:.2rem .4rem}.item.is-selected{border-color:#000}.ProseMirror{outline:none;caret-color:#171717;word-break:break-word}.ProseMirror-focused:focus-visible{outline:none}.ProseMirror:not(.ProseMirror-focused) p.is-editor-empty:first-child:before{content:attr(data-placeholder);float:left;color:#999;pointer-events:none;height:0}.ProseMirror-selectednode video,img.ProseMirror-selectednode{outline:2px solid #E2E2E2}.mention{font-weight:600;-webkit-box-decoration-break:clone;box-decoration-break:clone}.prose table p{margin:0}.ProseMirror table .selectedCell:after{z-index:2;position:absolute;content:"";inset:0;pointer-events:none;background:#E3F1FD;opacity:.3}.ProseMirror table .column-resize-handle{position:absolute;right:-1px;top:0;bottom:-2px;width:4px;background-color:#e3f1fd;pointer-events:none}.resize-cursor{cursor:ew-resize;cursor:col-resize}.ProseMirror mark{border-radius:3px;padding:0 2px}

View File

@@ -0,0 +1,45 @@
var u = Object.defineProperty;
var o = Object.getOwnPropertySymbols;
var i = Object.prototype.hasOwnProperty,
c = Object.prototype.propertyIsEnumerable;
var n = (t, e, r) =>
e in t
? u(t, e, {
enumerable: !0,
configurable: !0,
writable: !0,
value: r,
})
: (t[e] = r),
a = (t, e) => {
for (var r in e || (e = {})) i.call(e, r) && n(t, r, e[r]);
if (o) for (var r of o(e)) c.call(e, r) && n(t, r, e[r]);
return t;
};
import { ac as s, ad as f } from "./frappe-ui.f2211ca2.js";
function y(t) {
s(a({ position: "bottom-right" }, t));
}
function d(t) {
return f(t).value;
}
function g(t) {
if (!t) return "";
const [e, r] = t.split(":").map(Number),
m = new Date(0, 0, 0, e, r);
return new Intl.DateTimeFormat("en-US", {
hour: "numeric",
minute: "numeric",
hour12: !0,
}).format(m);
}
function h(t, e) {
return t
? t.toLocaleString("en-IN", {
maximumFractionDigits: 0,
style: "currency",
currency: e,
})
: "";
}
export { h as a, y as c, g as f, d as t };

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +0,0 @@
import {
c as u,
a as l,
_ as i,
b as f,
r as p,
o as d,
d as m,
e as _,
f as h,
g as y,
h as g,
s as v,
i as L,
} from "./frappe-ui.8966d601.js";
(function () {
const o = document.createElement("link").relList;
if (o && o.supports && o.supports("modulepreload")) return;
for (const e of document.querySelectorAll('link[rel="modulepreload"]'))
c(e);
new MutationObserver((e) => {
for (const t of e)
if (t.type === "childList")
for (const s of t.addedNodes)
s.tagName === "LINK" && s.rel === "modulepreload" && c(s);
}).observe(document, { childList: !0, subtree: !0 });
function n(e) {
const t = {};
return (
e.integrity && (t.integrity = e.integrity),
e.referrerpolicy && (t.referrerPolicy = e.referrerpolicy),
e.crossorigin === "use-credentials"
? (t.credentials = "include")
: e.crossorigin === "anonymous"
? (t.credentials = "omit")
: (t.credentials = "same-origin"),
t
);
}
function c(e) {
if (e.ep) return;
e.ep = !0;
const t = n(e);
fetch(e.href, t);
}
})();
const E = [
{
path: "/",
name: "Home",
component: () =>
i(
() => import("./Home.24891fef.js"),
[
"assets/Home.24891fef.js",
"assets/frappe-ui.8966d601.js",
"assets/frappe-ui.e894a05e.css",
]
),
},
{
path: "/courses",
name: "Courses",
component: () =>
i(
() => import("./Courses.10129947.js"),
[
"assets/Courses.10129947.js",
"assets/frappe-ui.8966d601.js",
"assets/frappe-ui.e894a05e.css",
"assets/Courses.4fd15046.css",
]
),
},
];
let O = u({ history: l("/"), routes: E });
const P = {};
function b(a, o) {
const n = p("router-view");
return d(), m("div", null, [_(n)]);
}
const A = f(P, [["render", b]]);
let r = h(A);
v("resourceFetcher", L);
r.use(O);
r.use(y);
r.component("Button", g);
r.mount("#app");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
import { c as e } from "./index.43e529db.js";
const n = e("PlusIcon", [
["line", { x1: "12", x2: "12", y1: "5", y2: "19", key: "pwfkuu" }],
["line", { x1: "5", x2: "19", y1: "12", y2: "12", key: "13b5wn" }],
]);
export { n as P };

View File

@@ -0,0 +1,11 @@
import { c as o } from "./index.43e529db.js";
const c = o("StarIcon", [
[
"polygon",
{
points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2",
key: "8f66p6",
},
],
]);
export { c as S };

View File

@@ -5,10 +5,10 @@
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Frappe UI App</title>
<script type="module" crossorigin src="/assets/index.52b6e5cb.js"></script>
<link rel="modulepreload" crossorigin href="/assets/frappe-ui.8966d601.js">
<link rel="stylesheet" href="/assets/frappe-ui.e894a05e.css">
<link rel="stylesheet" href="/assets/index.943a3ec2.css">
<script type="module" crossorigin src="/assets/index.43e529db.js"></script>
<link rel="modulepreload" crossorigin href="/assets/frappe-ui.f2211ca2.js">
<link rel="stylesheet" href="/assets/frappe-ui.7692ed2d.css">
<link rel="stylesheet" href="/assets/index.64bc1bc1.css">
</head>
<body>
<div id="app"></div>

View File

@@ -1,7 +1,7 @@
from frappe import _
import frappe
from frappe.utils import getdate
from lms.www.utils import get_assessments, is_student
from lms.www.utils import is_student
from lms.lms.utils import (
has_course_moderator_role,
has_course_evaluator_role,
@@ -12,6 +12,7 @@ from lms.lms.utils import (
get_lesson_url,
get_lesson_icon,
get_membership,
get_assessments,
)

View File

@@ -61,89 +61,6 @@ def get_current_lesson_details(lesson_number, context, is_edit=False):
return lesson_info
def get_assessments(batch, member=None):
if not member:
member = frappe.session.user
assessments = frappe.get_all(
"LMS Assessment",
{"parent": batch},
["name", "assessment_type", "assessment_name"],
)
for assessment in assessments:
if assessment.assessment_type == "LMS Assignment":
assessment = get_assignment_details(assessment, member)
elif assessment.assessment_type == "LMS Quiz":
assessment = get_quiz_details(assessment, member)
return assessments
def get_assignment_details(assessment, member):
assessment.title = frappe.db.get_value(
"LMS Assignment", assessment.assessment_name, "title"
)
existing_submission = frappe.db.exists(
{
"doctype": "LMS Assignment Submission",
"member": member,
"assignment": assessment.assessment_name,
}
)
assessment.completed = False
if existing_submission:
assessment.submission = frappe.db.get_value(
"LMS Assignment Submission",
existing_submission,
["name", "status", "comments"],
as_dict=True,
)
assessment.completed = True
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
submission_name = existing_submission if existing_submission else "new-submission"
assessment.url = (
f"/assignment-submission/{assessment.assessment_name}/{submission_name}"
)
return assessment
def get_quiz_details(assessment, member):
assessment_details = frappe.db.get_value(
"LMS Quiz", assessment.assessment_name, ["title", "passing_percentage"], as_dict=1
)
assessment.title = assessment_details.title
existing_submission = frappe.get_all(
"LMS Quiz Submission",
{
"member": member,
"quiz": assessment.assessment_name,
},
["name", "score", "percentage"],
order_by="percentage desc",
)
if len(existing_submission):
assessment.submission = existing_submission[0]
assessment.completed = False
if assessment.submission:
assessment.completed = True
assessment.edit_url = f"/quizzes/{assessment.assessment_name}"
submission_name = (
existing_submission[0].name if len(existing_submission) else "new-submission"
)
assessment.url = f"/quiz-submission/{assessment.assessment_name}/{submission_name}"
return assessment
def is_student(batch, member=None):
if not member:
member = frappe.session.user

4057
yarn.lock

File diff suppressed because it is too large Load Diff