@@ -65,6 +65,10 @@
|
||||
}"
|
||||
>
|
||||
<Button variant="solid" class="w-full mt-4">
|
||||
<template #prefix>
|
||||
<Settings v-if="isModerator" class="size-4 stroke-1.5" />
|
||||
<LogIn v-else class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
<span>
|
||||
{{ isModerator ? __('Manage Batch') : __('Visit Batch') }}
|
||||
</span>
|
||||
@@ -85,6 +89,9 @@
|
||||
"
|
||||
>
|
||||
<Button v-if="!isStudent" class="w-full mt-4" variant="solid">
|
||||
<template #prefix>
|
||||
<CreditCard class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
<span>
|
||||
{{ __('Register Now') }}
|
||||
</span>
|
||||
@@ -100,6 +107,9 @@
|
||||
"
|
||||
@click="enrollInBatch()"
|
||||
>
|
||||
<template #prefix>
|
||||
<GraduationCap class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
{{ __('Enroll Now') }}
|
||||
</Button>
|
||||
<router-link
|
||||
@@ -112,6 +122,9 @@
|
||||
}"
|
||||
>
|
||||
<Button class="w-full mt-2">
|
||||
<template #prefix>
|
||||
<Pencil class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
<span>
|
||||
{{ __('Edit') }}
|
||||
</span>
|
||||
@@ -122,8 +135,17 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { inject, computed } from 'vue'
|
||||
import { Badge, Button, createResource, toast } from 'frappe-ui'
|
||||
import { BookOpen, Clock, Globe } from 'lucide-vue-next'
|
||||
import { Button, createResource, toast } from 'frappe-ui'
|
||||
import {
|
||||
BookOpen,
|
||||
Clock,
|
||||
CreditCard,
|
||||
Globe,
|
||||
GraduationCap,
|
||||
LogIn,
|
||||
Pencil,
|
||||
Settings,
|
||||
} from 'lucide-vue-next'
|
||||
import { formatNumberIntoCurrency, formatTime } from '@/utils'
|
||||
import DateRange from '@/components/Common/DateRange.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="flex justify-between space-x-10 text-base">
|
||||
<div class="flex justify-between space-x-10 text-base mt-10">
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between space-x-5 mb-4">
|
||||
<div class="text-xl font-semibold text-ink-gray-6">
|
||||
<!-- <div class="text-xl font-semibold text-ink-gray-6">
|
||||
{{ __('{0} Members').format(memberCount) }}
|
||||
</div>
|
||||
</div> -->
|
||||
<FormControl
|
||||
v-model="searchFilter"
|
||||
:label="__('Search by Member Name')"
|
||||
:placeholder="__('Search by Member Name')"
|
||||
type="text"
|
||||
class="w-1/2"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="max-h-[70vh] overflow-y-auto">
|
||||
@@ -90,13 +90,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4 self-start w-full space-y-5">
|
||||
<NumberChart
|
||||
class="border rounded-md"
|
||||
:config="{
|
||||
title: __('Average Progress %'),
|
||||
value: chartDetails.data?.average_progress || 0,
|
||||
}"
|
||||
/>
|
||||
<div class="flex items-center space-x-4">
|
||||
<NumberChart
|
||||
class="border rounded-md w-full"
|
||||
:config="{
|
||||
title: __('Enrollments'),
|
||||
value: memberCount || 0,
|
||||
}"
|
||||
/>
|
||||
<NumberChart
|
||||
class="border rounded-md w-full"
|
||||
:config="{
|
||||
title: __('Average Progress %'),
|
||||
value: chartDetails.data?.average_progress || 0,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<DonutChart
|
||||
:config="{
|
||||
data: chartDetails.data?.progress_distribution || [],
|
||||
|
||||
@@ -88,9 +88,31 @@ const update = () => {
|
||||
)
|
||||
}
|
||||
|
||||
watch(branding, (newData) => {
|
||||
if (newData && !isDirty.value) {
|
||||
isDirty.value = true
|
||||
}
|
||||
watch(branding, (updatedDoc) => {
|
||||
let textFields = []
|
||||
let imageFields = []
|
||||
|
||||
props.fields.forEach((f) => {
|
||||
if (f.type === 'Upload') {
|
||||
imageFields.push(f.name)
|
||||
} else {
|
||||
textFields.push(f.name)
|
||||
}
|
||||
})
|
||||
|
||||
textFields.forEach((field) => {
|
||||
if (updatedDoc.data[field] != updatedDoc.previousData[field]) {
|
||||
isDirty.value = true
|
||||
}
|
||||
})
|
||||
|
||||
imageFields.forEach((field) => {
|
||||
if (
|
||||
updatedDoc.data[field] &&
|
||||
updatedDoc.data[field].file_url != updatedDoc.previousData[field].file_url
|
||||
) {
|
||||
isDirty.value = true
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -37,8 +37,13 @@
|
||||
<component
|
||||
v-if="activeTab.template"
|
||||
:is="activeTab.template"
|
||||
:label="activeTab.label"
|
||||
:description="activeTab.description"
|
||||
v-bind="{
|
||||
label: activeTab.label,
|
||||
description: activeTab.description,
|
||||
...(activeTab.label === 'Branding'
|
||||
? { fields: activeTab.fields }
|
||||
: {}),
|
||||
}"
|
||||
/>
|
||||
<PaymentSettings
|
||||
v-else-if="activeTab.label === 'Payment Gateway'"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<template #prefix>
|
||||
<Plus class="w-4 h-4" />
|
||||
</template>
|
||||
{{ __('New') }}
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -4,9 +4,16 @@
|
||||
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
<Breadcrumbs class="h-7" :items="breadcrumbs" />
|
||||
<Button variant="solid" @click="saveBatch()">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button v-if="batchDetail.data?.name" @click="deleteBatch">
|
||||
<template #icon>
|
||||
<Trash2 class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button variant="solid" @click="saveBatch()">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="py-5">
|
||||
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
||||
@@ -303,10 +310,11 @@
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
getCurrentInstance,
|
||||
inject,
|
||||
reactive,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
reactive,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import {
|
||||
@@ -318,9 +326,11 @@ import {
|
||||
createResource,
|
||||
usePageMeta,
|
||||
toast,
|
||||
call,
|
||||
Toast,
|
||||
} from 'frappe-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Image } from 'lucide-vue-next'
|
||||
import { Image, Trash2 } from 'lucide-vue-next'
|
||||
import { capture } from '@/telemetry'
|
||||
import { useOnboarding } from 'frappe-ui/frappe'
|
||||
import { sessionStore } from '../stores/session'
|
||||
@@ -338,6 +348,8 @@ const user = inject('$user')
|
||||
const { brand } = sessionStore()
|
||||
const { updateOnboardingStep } = useOnboarding('learning')
|
||||
const instructors = ref([])
|
||||
const app = getCurrentInstance()
|
||||
const { $dialog } = app.appContext.config.globalProperties
|
||||
|
||||
const props = defineProps({
|
||||
batchName: {
|
||||
@@ -539,6 +551,38 @@ const editBatchDetails = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const deleteBatch = () => {
|
||||
$dialog({
|
||||
title: __('Confirm your action to delete'),
|
||||
message: __(
|
||||
'Deleting this batch will also delete all its data including enrolled students, linked courses, assessments, feedback and discussions. Are you sure you want to continue?'
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: __('Delete'),
|
||||
theme: 'red',
|
||||
variant: 'solid',
|
||||
onClick({ close }) {
|
||||
trashBatch(close)
|
||||
close()
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const trashBatch = (close) => {
|
||||
call('lms.lms.api.delete_batch', {
|
||||
batch: props.batchName,
|
||||
}).then(() => {
|
||||
toast.success(__('Batch deleted successfully'))
|
||||
close()
|
||||
router.push({
|
||||
name: 'Batches',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const saveImage = (file) => {
|
||||
batch.image = file
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
{{ __('New') }}
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</router-link>
|
||||
</header>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
{{ __('New') }}
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</router-link>
|
||||
</header>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
{{ __('New') }}
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<template #prefix>
|
||||
<Plus class="w-4 h-4" />
|
||||
</template>
|
||||
{{ __('New') }}
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</header>
|
||||
<div class="py-5 mx-5">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div class="text-xl font-semibold text-ink-gray-7">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-lg font-semibold text-ink-gray-7">
|
||||
{{
|
||||
quizzes.data?.length
|
||||
? __('{0} Quizzes').format(quizzes.data.length)
|
||||
@@ -263,7 +263,7 @@ const quizColumns = computed(() => {
|
||||
label: __('Modified'),
|
||||
key: 'modified',
|
||||
width: 1,
|
||||
align: 'right',
|
||||
align: 'center',
|
||||
icon: 'clock',
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user