refactor: batch form
This commit is contained in:
@@ -1,9 +1,22 @@
|
||||
<template>
|
||||
<div v-if="batch.data" class="">
|
||||
<header
|
||||
class="sticky top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
||||
class="sticky top-0 z-10 border-b flex items-center justify-between bg-surface-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<div v-if="tabIndex == 5" class="flex items-center space-x-2">
|
||||
<Badge v-if="childRef?.isDirty" theme="orange">
|
||||
{{ __('Not Saved') }}
|
||||
</Badge>
|
||||
<Button @click="childRef.trashCourse()">
|
||||
<template #icon>
|
||||
<Trash2 class="w-4 h-4 stroke-1.5" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button variant="solid" @click="childRef.submitCourse()">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<BatchOverview v-if="!isAdmin && !isStudent" :batch="batch" />
|
||||
@@ -18,17 +31,25 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, inject, markRaw, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
Laptop,
|
||||
List,
|
||||
Mail,
|
||||
MessageCircle,
|
||||
Settings2,
|
||||
Trash2,
|
||||
TrendingUp,
|
||||
} from 'lucide-vue-next'
|
||||
import { Breadcrumbs, createResource, Tabs, usePageMeta } from 'frappe-ui'
|
||||
import { computed, inject, markRaw, ref, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
Badge,
|
||||
Breadcrumbs,
|
||||
Button,
|
||||
createResource,
|
||||
Tabs,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import AdminBatchDashboard from '@/pages/Batches/components/AdminBatchDashboard.vue'
|
||||
import StudentBatchDashboard from '@/pages/Batches/components/BatchDashboard.vue'
|
||||
@@ -39,10 +60,11 @@ import BatchForm from '@/pages/Batches/BatchForm.vue'
|
||||
import Discussions from '@/components/Discussions.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { brand } = sessionStore()
|
||||
const user = inject('$user')
|
||||
const childRef = ref(null)
|
||||
const tabIndex = ref(5)
|
||||
const tabIndex = ref(0)
|
||||
const tabs = ref([])
|
||||
|
||||
const props = defineProps({
|
||||
@@ -52,6 +74,24 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const updateTabIndex = () => {
|
||||
const hash = route.hash
|
||||
if (hash) {
|
||||
tabs.value.forEach((tab, index) => {
|
||||
if (tab.label?.toLowerCase() === hash.replace('#', '')) {
|
||||
tabIndex.value = index
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(tabIndex, () => {
|
||||
const tab = tabs.value[tabIndex.value]
|
||||
if (tab.label != route.hash.replace('#', '')) {
|
||||
router.push({ ...route, hash: `#${tab.label.toLowerCase()}` })
|
||||
}
|
||||
})
|
||||
|
||||
const batch = createResource({
|
||||
url: 'lms.lms.utils.get_batch_details',
|
||||
cache: ['batch', props.batchName],
|
||||
@@ -68,6 +108,7 @@ const batch = createResource({
|
||||
|
||||
watch(batch, () => {
|
||||
updateTabs()
|
||||
updateTabIndex()
|
||||
})
|
||||
|
||||
const updateTabs = () => {
|
||||
|
||||
@@ -15,254 +15,244 @@
|
||||
</Button>
|
||||
</div>
|
||||
</header> -->
|
||||
<div class="py-5">
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||
{{ __('Details') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="space-y-5">
|
||||
<div class="grid grid-cols-[3fr,2fr]">
|
||||
<div v-if="batchDetail.doc" class="py-5 h-[88vh] overflow-y-auto">
|
||||
<div class="px-5 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||
<FormControl
|
||||
v-model="batch.title"
|
||||
:label="__('Title')"
|
||||
:required="true"
|
||||
class="w-full"
|
||||
v-model="batchDetail.doc.published"
|
||||
type="checkbox"
|
||||
:label="__('Published')"
|
||||
@change="makeFormDirty"
|
||||
/>
|
||||
<MultiSelect
|
||||
v-model="instructors"
|
||||
doctype="Course Evaluator"
|
||||
:label="__('Instructors')"
|
||||
:required="true"
|
||||
:onCreate="(close) => openSettings('Evaluators', close)"
|
||||
:filters="{ ignore_user_type: 1 }"
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.allow_self_enrollment"
|
||||
type="checkbox"
|
||||
:label="__('Allow Self Enrollment')"
|
||||
@change="makeFormDirty"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.certification"
|
||||
type="checkbox"
|
||||
:label="__('Certification')"
|
||||
@change="makeFormDirty"
|
||||
/>
|
||||
</div>
|
||||
<FormControl
|
||||
v-model="batch.description"
|
||||
:label="__('Short Description')"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
:placeholder="__('Short description of the batch')"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||
{{ __('Settings') }}
|
||||
<div class="px-5 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||
{{ __('Details') }}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.title"
|
||||
:label="__('Title')"
|
||||
:required="true"
|
||||
class="w-full"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.start_date"
|
||||
:label="__('Batch Start Date')"
|
||||
type="date"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.end_date"
|
||||
:label="__('Batch End Date')"
|
||||
type="date"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.seat_count"
|
||||
:label="__('Seat Count')"
|
||||
type="number"
|
||||
class="mb-4"
|
||||
:placeholder="__('Number of seats available')"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.start_time"
|
||||
:label="__('Session Start Time')"
|
||||
type="time"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.end_time"
|
||||
:label="__('Session End Time')"
|
||||
type="time"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.timezone"
|
||||
:label="__('Timezone')"
|
||||
type="text"
|
||||
:placeholder="__('Example: IST (+5:30)')"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
|
||||
<Link
|
||||
doctype="LMS Category"
|
||||
:label="__('Category')"
|
||||
v-model="batchDetail.doc.category"
|
||||
:onCreate="(value, close) => openSettings('Categories', close)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||
|
||||
<div class="px-5 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.description"
|
||||
:label="__('Short Description')"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="__('Short description of the batch')"
|
||||
:required="true"
|
||||
/>
|
||||
<MultiSelect
|
||||
v-model="instructors"
|
||||
doctype="Course Evaluator"
|
||||
:label="__('Instructors')"
|
||||
:required="true"
|
||||
:onCreate="(close) => openSettings('Evaluators', close)"
|
||||
:filters="{ ignore_user_type: 1 }"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-ink-gray-5 mb-1">
|
||||
{{ __('Batch Details') }}
|
||||
<span class="text-ink-red-3">*</span>
|
||||
</label>
|
||||
<TextEditor
|
||||
:content="batchDetail.doc.batch_details"
|
||||
@change="(val) => (batchDetail.doc.batch_details = val)"
|
||||
:editable="true"
|
||||
:fixedMenu="true"
|
||||
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[16rem] overflow-y-scroll mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.medium"
|
||||
type="select"
|
||||
:options="mediumOptions"
|
||||
:label="__('Medium')"
|
||||
class="mb-4"
|
||||
/>
|
||||
<Link
|
||||
doctype="Email Template"
|
||||
:label="__('Email Template')"
|
||||
v-model="batchDetail.doc.confirmation_email_template"
|
||||
:onCreate="
|
||||
(value, close) => {
|
||||
openSettings('Email Templates', close)
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Link
|
||||
doctype="LMS Zoom Settings"
|
||||
:label="__('Zoom Account')"
|
||||
v-model="batchDetail.doc.zoom_account"
|
||||
:onCreate="
|
||||
(value, close) => {
|
||||
openSettings('Zoom Accounts', close)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.evaluation_end_date"
|
||||
:label="__('Evaluation End Date')"
|
||||
type="date"
|
||||
class="mb-4"
|
||||
/>
|
||||
<Uploader
|
||||
v-model="batchDetail.doc.video_link"
|
||||
:label="__('Preview Video')"
|
||||
type="video"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Pricing') }}
|
||||
</div>
|
||||
<FormControl
|
||||
v-model="batch.published"
|
||||
v-model="batchDetail.doc.paid_batch"
|
||||
type="checkbox"
|
||||
:label="__('Published')"
|
||||
:label="__('Paid Batch')"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.allow_self_enrollment"
|
||||
type="checkbox"
|
||||
:label="__('Allow self enrollment')"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.certification"
|
||||
type="checkbox"
|
||||
:label="__('Certification')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||
{{ __('Date and Time') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
<div class="space-y-5">
|
||||
<div
|
||||
v-if="batchDetail.doc.paid_batch"
|
||||
class="grid grid-cols-1 md:grid-cols-2 gap-5"
|
||||
>
|
||||
<FormControl
|
||||
v-model="batch.start_date"
|
||||
:label="__('Batch Start Date')"
|
||||
type="date"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.end_date"
|
||||
:label="__('Batch End Date')"
|
||||
type="date"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batch.start_time"
|
||||
:label="__('Session Start Time')"
|
||||
type="time"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.end_time"
|
||||
:label="__('Session End Time')"
|
||||
type="time"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batch.timezone"
|
||||
:label="__('Timezone')"
|
||||
type="text"
|
||||
:placeholder="__('Example: IST (+5:30)')"
|
||||
class="mb-4"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.evaluation_end_date"
|
||||
:label="__('Evaluation End Date')"
|
||||
type="date"
|
||||
class="mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5 border-b mb-5">
|
||||
<div>
|
||||
<label class="block text-sm text-ink-gray-5 mb-1">
|
||||
{{ __('Batch Details') }}
|
||||
<span class="text-ink-red-3">*</span>
|
||||
</label>
|
||||
<TextEditor
|
||||
:content="batch.batch_details"
|
||||
@change="(val) => (batch.batch_details = val)"
|
||||
:editable="true"
|
||||
:fixedMenu="true"
|
||||
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[20rem] overflow-y-scroll mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5 border-b mb-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||
{{ __('Configurations') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batch.seat_count"
|
||||
:label="__('Seat Count')"
|
||||
v-model="batchDetail.doc.amount"
|
||||
:label="__('Amount')"
|
||||
type="number"
|
||||
class="mb-4"
|
||||
:placeholder="__('Number of seats available')"
|
||||
/>
|
||||
<Link
|
||||
doctype="Email Template"
|
||||
:label="__('Email Template')"
|
||||
v-model="batch.confirmation_email_template"
|
||||
:onCreate="
|
||||
(value, close) => {
|
||||
openSettings('Email Templates', close)
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Link
|
||||
doctype="LMS Zoom Settings"
|
||||
:label="__('Zoom Account')"
|
||||
v-model="batch.zoom_account"
|
||||
:onCreate="
|
||||
(value, close) => {
|
||||
openSettings('Zoom Accounts', close)
|
||||
}
|
||||
"
|
||||
doctype="Currency"
|
||||
v-model="batchDetail.doc.currency"
|
||||
:filters="{ enabled: 1 }"
|
||||
:label="__('Currency')"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-5 space-y-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Meta Tags') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<FormControl
|
||||
v-model="batch.medium"
|
||||
type="select"
|
||||
:options="[
|
||||
{
|
||||
label: 'Online',
|
||||
value: 'Online',
|
||||
},
|
||||
{
|
||||
label: 'Offline',
|
||||
value: 'Offline',
|
||||
},
|
||||
]"
|
||||
:label="__('Medium')"
|
||||
class="mb-4"
|
||||
v-model="meta.description"
|
||||
:label="__('Meta Description')"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
/>
|
||||
<Link
|
||||
doctype="LMS Category"
|
||||
:label="__('Category')"
|
||||
v-model="batch.category"
|
||||
:onCreate="(value, close) => openSettings('Categories', close)"
|
||||
<FormControl
|
||||
v-model="meta.keywords"
|
||||
:label="__('Meta Keywords')"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="__('Comma separated keywords')"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<Uploader
|
||||
v-model="batch.video_link"
|
||||
:label="__('Preview Video')"
|
||||
type="video"
|
||||
v-model="batchDetail.doc.meta_image"
|
||||
:label="__('Meta Image')"
|
||||
type="image"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Pricing') }}
|
||||
<div class="border-l min-w-0">
|
||||
<div class="border-b p-4">
|
||||
<BatchCourses :batch="batch.data?.name" />
|
||||
</div>
|
||||
<FormControl
|
||||
v-model="batch.paid_batch"
|
||||
type="checkbox"
|
||||
:label="__('Paid Batch')"
|
||||
/>
|
||||
<div
|
||||
v-if="batch.paid_batch"
|
||||
class="grid grid-cols-1 md:grid-cols-3 gap-5"
|
||||
>
|
||||
<FormControl
|
||||
v-model="batch.amount"
|
||||
:label="__('Amount')"
|
||||
type="number"
|
||||
/>
|
||||
<Link
|
||||
doctype="Currency"
|
||||
v-model="batch.currency"
|
||||
:filters="{ enabled: 1 }"
|
||||
:label="__('Currency')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 md:px-20 pb-5 space-y-5 border-b">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Meta Tags') }}
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<Uploader
|
||||
v-model="batch.meta_image"
|
||||
:label="__('Meta Image')"
|
||||
type="image"
|
||||
:required="false"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="meta.description"
|
||||
:label="__('Meta Description')"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="meta.keywords"
|
||||
:label="__('Meta Keywords')"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
:placeholder="__('Comma separated keywords for SEO')"
|
||||
/>
|
||||
<div class="p-4">
|
||||
<Assessments :batch="batch.data?.name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -277,13 +267,14 @@ import {
|
||||
onBeforeUnmount,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
FormControl,
|
||||
Button,
|
||||
TextEditor,
|
||||
createResource,
|
||||
createDocumentResource,
|
||||
usePageMeta,
|
||||
toast,
|
||||
call,
|
||||
@@ -302,6 +293,8 @@ import { sessionStore } from '@/stores/session'
|
||||
import Uploader from '@/components/Controls/Uploader.vue'
|
||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import BatchCourses from '@/pages/Batches/components/BatchCourses.vue'
|
||||
import Assessments from '@/pages/Batches/components/Assessments.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const user = inject('$user')
|
||||
@@ -311,6 +304,13 @@ const instructors = ref([])
|
||||
const app = getCurrentInstance()
|
||||
const { capture } = useTelemetry()
|
||||
const { $dialog } = app.appContext.config.globalProperties
|
||||
const isDirty = ref(false)
|
||||
const originalDoc = ref(null)
|
||||
|
||||
const meta = reactive({
|
||||
description: '',
|
||||
keywords: '',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
batch: {
|
||||
@@ -319,36 +319,6 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const batch = reactive({
|
||||
title: '',
|
||||
published: false,
|
||||
description: '',
|
||||
batch_details: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
timezone: '',
|
||||
evaluation_end_date: '',
|
||||
confirmation_email_template: '',
|
||||
seat_count: '',
|
||||
medium: '',
|
||||
category: '',
|
||||
allow_self_enrollment: false,
|
||||
certification: false,
|
||||
meta_image: null,
|
||||
paid_batch: false,
|
||||
currency: '',
|
||||
amount: 0,
|
||||
zoom_account: '',
|
||||
video_link: '',
|
||||
})
|
||||
|
||||
const meta = reactive({
|
||||
description: '',
|
||||
keywords: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!user.data) window.location.href = '/login'
|
||||
if (props.batchName != 'new') {
|
||||
@@ -396,28 +366,29 @@ const newBatch = createResource({
|
||||
},
|
||||
})
|
||||
|
||||
const batchDetail = createResource({
|
||||
url: 'frappe.client.get',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'LMS Batch',
|
||||
name: props.batchName,
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
updateBatchData(data)
|
||||
},
|
||||
const batchDetail = createDocumentResource({
|
||||
doctype: 'LMS Batch',
|
||||
name: props.batch.data?.name,
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const updateBatchData = (data) => {
|
||||
Object.keys(data).forEach((key) => {
|
||||
watch(
|
||||
() => batchDetail.doc,
|
||||
() => {
|
||||
getMetaInfo('batches', batchDetail.doc?.name, meta)
|
||||
updateBatchData()
|
||||
}
|
||||
)
|
||||
|
||||
const updateBatchData = () => {
|
||||
Object.keys(batchDetail.doc).forEach((key) => {
|
||||
if (key == 'instructors') {
|
||||
data.instructors.forEach((instructor) => {
|
||||
batchDetail.doc.instructors.forEach((instructor) => {
|
||||
instructors.value.push(instructor.instructor)
|
||||
})
|
||||
} else if (['start_time', 'end_time'].includes(key)) {
|
||||
batch[key] = formatTime(data[key])
|
||||
} else if (Object.hasOwn(batch, key)) batch[key] = data[key]
|
||||
batchDetail.doc[key] = formatTime(batchDetail.doc[key])
|
||||
}
|
||||
})
|
||||
let checkboxes = [
|
||||
'published',
|
||||
@@ -427,8 +398,9 @@ const updateBatchData = (data) => {
|
||||
]
|
||||
for (let idx in checkboxes) {
|
||||
let key = checkboxes[idx]
|
||||
batch[key] = batch[key] ? true : false
|
||||
batchDetail.doc[key] = batchDetail.doc[key] ? true : false
|
||||
}
|
||||
originalDoc.value = batchDetail.doc
|
||||
}
|
||||
|
||||
const formatTime = (timeStr) => {
|
||||
@@ -469,59 +441,35 @@ const validateFields = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const saveBatch = () => {
|
||||
const submitBatch = () => {
|
||||
validateFields()
|
||||
if (batchDetail.data) {
|
||||
editBatchDetails()
|
||||
} else {
|
||||
createNewBatch()
|
||||
}
|
||||
updateBatch()
|
||||
}
|
||||
|
||||
const createNewBatch = () => {
|
||||
newBatch.submit(
|
||||
{},
|
||||
const updateBatch = () => {
|
||||
batchDetails.setValue.submit(
|
||||
{
|
||||
...batchDetail.doc,
|
||||
instructors: instructors.value.map((instructor) => ({
|
||||
instructor: instructor,
|
||||
})),
|
||||
},
|
||||
{
|
||||
onSuccess(data) {
|
||||
if (user.data?.is_system_manager) {
|
||||
updateOnboardingStep('create_first_batch', true, false, () => {
|
||||
localStorage.setItem('firstBatch', data.name)
|
||||
})
|
||||
}
|
||||
updateMetaInfo('batches', data.name, meta)
|
||||
capture('batch_created')
|
||||
router.push({
|
||||
name: 'BatchDetail',
|
||||
params: {
|
||||
batchName: data.name,
|
||||
},
|
||||
})
|
||||
toast.success(__('Batch updated successfully'))
|
||||
isDirty.value = false
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const editBatchDetails = () => {
|
||||
editBatch.submit(
|
||||
{},
|
||||
{
|
||||
onSuccess(data) {
|
||||
updateMetaInfo('batches', data.name, meta)
|
||||
router.push({
|
||||
name: 'BatchDetail',
|
||||
params: {
|
||||
batchName: data.name,
|
||||
},
|
||||
})
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
},
|
||||
}
|
||||
)
|
||||
const makeFormDirty = () => {
|
||||
isDirty.value = true
|
||||
}
|
||||
|
||||
const deleteBatch = () => {
|
||||
@@ -556,37 +504,22 @@ const trashBatch = (close) => {
|
||||
})
|
||||
}
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
let crumbs = [
|
||||
const mediumOptions = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: __('Batches'),
|
||||
route: {
|
||||
name: 'Batches',
|
||||
},
|
||||
label: 'Online',
|
||||
value: 'Online',
|
||||
},
|
||||
{
|
||||
label: 'Offline',
|
||||
value: 'Offline',
|
||||
},
|
||||
]
|
||||
if (batchDetail.data) {
|
||||
crumbs.push({
|
||||
label: batchDetail.data.title,
|
||||
route: {
|
||||
name: 'BatchDetail',
|
||||
params: {
|
||||
batchName: props.batchName,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
crumbs.push({
|
||||
label: props.batchName == 'new' ? __('New Batch') : __('Edit Batch'),
|
||||
route: { name: 'BatchForm', params: { batchName: props.batchName } },
|
||||
})
|
||||
return crumbs
|
||||
})
|
||||
|
||||
usePageMeta(() => {
|
||||
return {
|
||||
title: props.batchName == 'new' ? 'New Batch' : batchDetail.data?.title,
|
||||
icon: brand.favicon,
|
||||
}
|
||||
defineExpose({
|
||||
submitBatch,
|
||||
trashBatch,
|
||||
isDirty,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-lg font-semibold text-ink-gray-9">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Assessments') }}
|
||||
</div>
|
||||
<Button v-if="canAddAssessments()" @click="showModal = true">
|
||||
@@ -16,6 +16,7 @@
|
||||
:columns="getAssessmentColumns()"
|
||||
:rows="assessments.data"
|
||||
row-key="name"
|
||||
class="border rounded-lg"
|
||||
:options="{
|
||||
showTooltip: false,
|
||||
getRowRoute: (row) => getRowRoute(row),
|
||||
@@ -23,7 +24,7 @@
|
||||
}"
|
||||
>
|
||||
<ListHeader
|
||||
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
|
||||
class="mb-2 grid items-center space-x-4 rounded-none rounded-t bg-surface-gray-2 p-2"
|
||||
>
|
||||
<ListHeaderItem :item="item" v-for="item in getAssessmentColumns()">
|
||||
<template #prefix="{ item }">
|
||||
@@ -210,12 +211,11 @@ const getAssessmentColumns = () => {
|
||||
{
|
||||
label: __('Assessment'),
|
||||
key: 'title',
|
||||
width: '25rem',
|
||||
},
|
||||
{
|
||||
label: __('Type'),
|
||||
key: 'assessment_type',
|
||||
width: '15rem',
|
||||
width: '10rem',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="font-medium text-ink-gray-9">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Courses') }}
|
||||
</div>
|
||||
<Button v-if="canSeeAddButton()" @click="openCourseModal()">
|
||||
@@ -11,11 +11,12 @@
|
||||
{{ __('Add') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="courses.data?.length">
|
||||
<div v-if="courses.data?.length" class="text-sm">
|
||||
<ListView
|
||||
:columns="getCoursesColumns()"
|
||||
:rows="courses.data"
|
||||
row-key="batch_course"
|
||||
class="border rounded-lg"
|
||||
:options="{
|
||||
showTooltip: false,
|
||||
selectable: user.data?.is_student ? false : true,
|
||||
@@ -26,16 +27,9 @@
|
||||
}"
|
||||
>
|
||||
<ListHeader
|
||||
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
|
||||
class="mb-2 grid items-center space-x-4 rounded-none rounded-t bg-surface-gray-2 p-2"
|
||||
>
|
||||
<ListHeaderItem :item="item" v-for="item in getCoursesColumns()">
|
||||
<template #prefix="{ item }">
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 stroke-1.5 ml-4"
|
||||
/>
|
||||
</template>
|
||||
</ListHeaderItem>
|
||||
</ListHeader>
|
||||
<ListRows>
|
||||
@@ -78,6 +72,7 @@ import { ref, inject } from 'vue'
|
||||
import BatchCourseModal from '@/components/Modals/BatchCourseModal.vue'
|
||||
import {
|
||||
createResource,
|
||||
createListResource,
|
||||
Button,
|
||||
ListHeader,
|
||||
ListHeaderItem,
|
||||
@@ -101,11 +96,14 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const courses = createResource({
|
||||
url: 'lms.lms.utils.get_batch_courses',
|
||||
params: {
|
||||
batch: props.batch,
|
||||
const courses = createListResource({
|
||||
doctype: 'Batch Course',
|
||||
filters: {
|
||||
parent: props.batch,
|
||||
parenttype: 'LMS Batch',
|
||||
},
|
||||
fields: ['name', 'course', 'title', 'evaluator'],
|
||||
parent: 'LMS Batch',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
@@ -118,17 +116,11 @@ const getCoursesColumns = () => {
|
||||
{
|
||||
label: 'Title',
|
||||
key: 'title',
|
||||
width: 2,
|
||||
},
|
||||
{
|
||||
label: 'Lessons',
|
||||
key: 'lessons',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: 'Enrollments',
|
||||
align: 'right',
|
||||
key: 'enrollments',
|
||||
label: 'Evaluator',
|
||||
key: 'evaluator',
|
||||
width: '10rem',
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
<template>
|
||||
<div class="space-y-10">
|
||||
<div class="space-y-10 p-5">
|
||||
<UpcomingEvaluations
|
||||
:batch="batch.data.name"
|
||||
:endDate="batch.data.evaluation_end_date"
|
||||
:courses="batch.data.courses"
|
||||
/>
|
||||
<Assessments :batch="batch.data.name" />
|
||||
<!-- <StudentHeatmap /> -->
|
||||
<BatchCourses :batch="batch.data.name" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import UpcomingEvaluations from '@/components/UpcomingEvaluations.vue'
|
||||
import Assessments from '@/pages/Batches/components/Assessments.vue'
|
||||
import BatchCourses from '@/pages/Batches/components/BatchCourses.vue'
|
||||
|
||||
const props = defineProps({
|
||||
batch: {
|
||||
|
||||
@@ -107,9 +107,10 @@
|
||||
</div>
|
||||
|
||||
<LiveClassModal
|
||||
v-if="showLiveClassModal"
|
||||
v-model="showLiveClassModal"
|
||||
:batch="props.batch"
|
||||
:zoomAccount="props.zoomAccount"
|
||||
v-model="showLiveClassModal"
|
||||
v-model:reloadLiveClasses="liveClasses"
|
||||
/>
|
||||
|
||||
@@ -144,7 +145,7 @@ const attendanceFor = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
batch: {
|
||||
type: String,
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
zoomAccount: String,
|
||||
@@ -153,7 +154,7 @@ const props = defineProps({
|
||||
const liveClasses = createListResource({
|
||||
doctype: 'LMS Live Class',
|
||||
filters: {
|
||||
batch_name: props.batch,
|
||||
batch_name: props.batch.data?.name,
|
||||
},
|
||||
fields: [
|
||||
'title',
|
||||
|
||||
@@ -373,7 +373,7 @@ const courseResource = createDocumentResource({
|
||||
watch(
|
||||
() => courseResource.doc,
|
||||
() => {
|
||||
check_permission()
|
||||
checkPermission()
|
||||
getMetaInfo('courses', courseResource.doc?.name, meta)
|
||||
updateCourseData()
|
||||
}
|
||||
@@ -516,7 +516,7 @@ const removeTag = (tag) => {
|
||||
makeFormDirty()
|
||||
}
|
||||
|
||||
const check_permission = () => {
|
||||
const checkPermission = () => {
|
||||
let user_is_instructor = false
|
||||
if (user.data?.is_moderator) return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user