refactor: new batch quick entry modal
This commit is contained in:
@@ -16,7 +16,12 @@
|
||||
<button
|
||||
class="flex w-full items-center justify-between focus:outline-none"
|
||||
:class="inputClasses"
|
||||
@click="() => togglePopover()"
|
||||
@click="
|
||||
() => {
|
||||
showOptions = true
|
||||
togglePopover()
|
||||
}
|
||||
"
|
||||
:disabled="attrs.readonly"
|
||||
>
|
||||
<div class="flex items-center w-[90%]">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
title: __('Add Course'),
|
||||
size: 'sm',
|
||||
title: __('Add a course to the batch'),
|
||||
size: 'lg',
|
||||
actions: [
|
||||
{
|
||||
label: __('Submit'),
|
||||
@@ -41,7 +41,7 @@
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Dialog, createResource, toast } from 'frappe-ui'
|
||||
import { Dialog, toast } from 'frappe-ui'
|
||||
import { ref, inject } from 'vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { useOnboarding } from 'frappe-ui/frappe'
|
||||
@@ -63,37 +63,28 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const createBatchCourse = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'Batch Course',
|
||||
parent: props.batch,
|
||||
parenttype: 'LMS Batch',
|
||||
parentfield: 'courses',
|
||||
course: course.value,
|
||||
evaluator: evaluator.value,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const addCourse = (close) => {
|
||||
createBatchCourse.submit(
|
||||
{},
|
||||
courses.value.insert.submit(
|
||||
{
|
||||
course: course.value,
|
||||
evaluator: evaluator.value,
|
||||
parent: props.batch,
|
||||
parenttype: 'LMS Batch',
|
||||
parentfield: 'courses',
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
if (user.data?.is_system_manager)
|
||||
updateOnboardingStep('add_batch_course')
|
||||
|
||||
close()
|
||||
courses.value.reload()
|
||||
course.value = null
|
||||
evaluator.value = null
|
||||
toast.success(__('Course added to batch successfully'))
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
console.log(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!endDateHasPassed" class="text-ink-gray-5">
|
||||
<div v-else-if="!endDateHasPassed" class="text-ink-gray-7">
|
||||
{{ __('Schedule an evaluation to get certified.') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,31 +4,74 @@
|
||||
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">
|
||||
<div v-if="tabIndex == 5 && isAdmin" class="flex items-center space-x-2">
|
||||
<Badge v-if="childRef?.isDirty" theme="orange">
|
||||
{{ __('Not Saved') }}
|
||||
</Badge>
|
||||
<Button @click="childRef.trashCourse()">
|
||||
<Button @click="childRef.deleteBatch()">
|
||||
<template #icon>
|
||||
<Trash2 class="w-4 h-4 stroke-1.5" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button variant="solid" @click="childRef.submitCourse()">
|
||||
<Button variant="solid" @click="childRef.submitBatch()">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else-if="isAdmin" class="space-x-2">
|
||||
<Button
|
||||
v-if="batch.data?.certification"
|
||||
@click="openCertificateDialog = true"
|
||||
>
|
||||
{{ __('Generate Certificates') }}
|
||||
</Button>
|
||||
<Button v-if="canMakeAnnouncement()" @click="openAnnouncementModal()">
|
||||
<span>
|
||||
{{ __('Make an Announcement') }}
|
||||
</span>
|
||||
<template #suffix>
|
||||
<SendIcon class="h-4 stroke-1.5" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<BatchOverview v-if="!isAdmin && !isStudent" :batch="batch" />
|
||||
<div v-else>
|
||||
<Tabs :tabs="tabs" v-model="tabIndex">
|
||||
<template #tab-panel="{ tab }">
|
||||
<component :is="tab.component" :batch="batch" ref="childRef" />
|
||||
<div v-if="tab.label == 'Discussions'" class="w-[75%] mx-auto mt-5">
|
||||
<Discussions
|
||||
doctype="LMS Batch"
|
||||
:docname="batch.data.name"
|
||||
:title="__('Discussions')"
|
||||
:key="batch.data.name"
|
||||
:singleThread="true"
|
||||
:scrollToBottom="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:is="tab.component"
|
||||
:batch="batch"
|
||||
ref="childRef"
|
||||
/>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BulkCertificates
|
||||
v-if="batch.data"
|
||||
v-model="openCertificateDialog"
|
||||
:batch="batch.data"
|
||||
/>
|
||||
<AnnouncementModal
|
||||
v-if="showAnnouncementModal"
|
||||
v-model="showAnnouncementModal"
|
||||
:batch="batch.data.name"
|
||||
:students="batch.data.students"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
@@ -36,6 +79,7 @@ import {
|
||||
List,
|
||||
Mail,
|
||||
MessageCircle,
|
||||
SendIcon,
|
||||
Settings2,
|
||||
Trash2,
|
||||
TrendingUp,
|
||||
@@ -55,8 +99,10 @@ import AdminBatchDashboard from '@/pages/Batches/components/AdminBatchDashboard.
|
||||
import StudentBatchDashboard from '@/pages/Batches/components/BatchDashboard.vue'
|
||||
import BatchOverview from '@/pages/Batches/BatchOverview.vue'
|
||||
import LiveClass from '@/pages/Batches/components/LiveClass.vue'
|
||||
import Announcements from '@/pages/Batches/components/Annoucements.vue'
|
||||
import Announcements from '@/pages/Batches/components/Announcements.vue'
|
||||
import AnnouncementModal from '@/pages/Batches/components/AnnouncementModal.vue'
|
||||
import BatchForm from '@/pages/Batches/BatchForm.vue'
|
||||
import BulkCertificates from '@/pages/Batches/components/BulkCertificates.vue'
|
||||
import Discussions from '@/components/Discussions.vue'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -66,6 +112,9 @@ const user = inject('$user')
|
||||
const childRef = ref(null)
|
||||
const tabIndex = ref(0)
|
||||
const tabs = ref([])
|
||||
const openCertificateDialog = ref(false)
|
||||
const showAnnouncementModal = ref(false)
|
||||
const readOnlyMode = window.read_only_mode
|
||||
|
||||
const props = defineProps({
|
||||
batchName: {
|
||||
@@ -145,6 +194,16 @@ const isStudent = computed(() => {
|
||||
return batch.data?.students?.includes(user.data?.name)
|
||||
})
|
||||
|
||||
const openAnnouncementModal = () => {
|
||||
showAnnouncementModal.value = true
|
||||
}
|
||||
|
||||
const canMakeAnnouncement = () => {
|
||||
if (readOnlyMode) return false
|
||||
if (!batch.data?.students?.length) return false
|
||||
return user.data?.is_moderator || user.data?.is_evaluator
|
||||
}
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
let crumbs = [{ label: __('Batches'), route: { name: 'Batches' } }]
|
||||
crumbs.push({
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<!-- <header
|
||||
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" />
|
||||
<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="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">
|
||||
@@ -143,7 +128,7 @@
|
||||
</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="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batchDetail.doc.medium"
|
||||
@@ -246,7 +231,7 @@
|
||||
</div>
|
||||
<div class="border-l min-w-0">
|
||||
<div class="border-b p-4">
|
||||
<BatchCourses :batch="batch.data?.name" />
|
||||
<BatchCourses :batch="batch" />
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<Assessments :batch="batch.data?.name" />
|
||||
@@ -270,11 +255,8 @@ import {
|
||||
} from 'vue'
|
||||
import {
|
||||
FormControl,
|
||||
Button,
|
||||
TextEditor,
|
||||
createResource,
|
||||
createDocumentResource,
|
||||
usePageMeta,
|
||||
toast,
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
@@ -286,7 +268,6 @@ import {
|
||||
updateMetaInfo,
|
||||
} from '@/utils'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Trash2 } from 'lucide-vue-next'
|
||||
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import Uploader from '@/components/Controls/Uploader.vue'
|
||||
@@ -320,10 +301,6 @@ const props = defineProps({
|
||||
|
||||
onMounted(() => {
|
||||
if (!user.data) window.location.href = '/login'
|
||||
if (props.batchName != 'new') {
|
||||
} else {
|
||||
capture('batch_form_opened')
|
||||
}
|
||||
window.addEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
@@ -342,23 +319,6 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
const newBatch = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'LMS Batch',
|
||||
meta_image: batch.image,
|
||||
video_link: batch.video_link,
|
||||
instructors: instructors.value.map((instructor) => ({
|
||||
instructor: instructor,
|
||||
})),
|
||||
...batch,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const batchDetail = createDocumentResource({
|
||||
doctype: 'LMS Batch',
|
||||
name: props.batch.data?.name,
|
||||
@@ -369,7 +329,6 @@ watch(
|
||||
() => batchDetail.doc,
|
||||
() => {
|
||||
if (!batchDetail.doc) return
|
||||
console.log('watch batch detail')
|
||||
getMetaInfo('batches', batchDetail.doc?.name, meta)
|
||||
updateBatchData()
|
||||
}
|
||||
@@ -378,6 +337,7 @@ watch(
|
||||
const updateBatchData = () => {
|
||||
Object.keys(batchDetail.doc).forEach((key) => {
|
||||
if (key == 'instructors') {
|
||||
instructors.value = []
|
||||
batchDetail.doc.instructors.forEach((instructor) => {
|
||||
instructors.value.push(instructor.instructor)
|
||||
})
|
||||
@@ -481,7 +441,7 @@ const deleteBatch = () => {
|
||||
|
||||
const trashBatch = (close) => {
|
||||
call('lms.lms.api.delete_batch', {
|
||||
batch: props.batchName,
|
||||
batch: props.batch.data.name,
|
||||
}).then(() => {
|
||||
toast.success(__('Batch deleted successfully'))
|
||||
close()
|
||||
@@ -506,7 +466,7 @@ const mediumOptions = computed(() => {
|
||||
|
||||
defineExpose({
|
||||
submitBatch,
|
||||
trashBatch,
|
||||
deleteBatch,
|
||||
isDirty,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -10,10 +10,7 @@
|
||||
label: __('New Batch'),
|
||||
icon: 'users',
|
||||
onClick() {
|
||||
router.push({
|
||||
name: 'BatchForm',
|
||||
params: { batchName: 'new' },
|
||||
})
|
||||
showBatchModal = true
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -45,20 +42,6 @@
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<!-- <router-link
|
||||
v-if="canCreateBatch()"
|
||||
:to="{
|
||||
name: 'BatchForm',
|
||||
params: { batchName: 'new' },
|
||||
}"
|
||||
>
|
||||
<Button variant="solid">
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
{{ __('Create') }}
|
||||
</Button>
|
||||
</router-link> -->
|
||||
</header>
|
||||
<div class="p-5 pb-10">
|
||||
<div
|
||||
@@ -125,6 +108,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<NewBatchModal
|
||||
v-if="showBatchModal"
|
||||
v-model="showBatchModal"
|
||||
:batches="batches"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
@@ -143,6 +131,7 @@ import { ChevronDown, Plus } from 'lucide-vue-next'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import BatchCard from '@/pages/Batches/components/BatchCard.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import NewBatchModal from '@/pages/Batches/components/NewBatchModal.vue'
|
||||
|
||||
const user = inject('$user')
|
||||
const dayjs = inject('$dayjs')
|
||||
@@ -159,6 +148,7 @@ const currentTab = ref(is_student.value ? 'all' : 'upcoming')
|
||||
const orderBy = ref('start_date')
|
||||
const readOnlyMode = window.read_only_mode
|
||||
const router = useRouter()
|
||||
const showBatchModal = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
setFiltersFromQuery()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<div class="w-[75%] mx-auto mt-5">
|
||||
<div class="font-semibold text-lg mb-5">
|
||||
{{ __('Announcements') }}
|
||||
</div>
|
||||
<div v-if="communications.data?.length">
|
||||
<div v-for="comm in communications.data">
|
||||
<div class="mb-8">
|
||||
@@ -27,17 +27,14 @@
|
||||
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 }">
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 stroke-1.5 ml-4"
|
||||
/>
|
||||
</template>
|
||||
</ListHeaderItem>
|
||||
</ListHeader>
|
||||
<ListRows>
|
||||
<ListRow :row="row" v-for="row in assessments.data">
|
||||
<ListRow
|
||||
:row="row"
|
||||
v-for="row in assessments.data"
|
||||
class="!rounded-none"
|
||||
>
|
||||
<template #default="{ column, item }">
|
||||
<ListRowItem :item="row[column.key]" :align="column.align">
|
||||
<div v-if="column.key == 'assessment_type'">
|
||||
@@ -58,7 +55,7 @@
|
||||
</template>
|
||||
</ListRow>
|
||||
</ListRows>
|
||||
<ListSelectBanner>
|
||||
<ListSelectBanner class="!min-w-0">
|
||||
<template #actions="{ unselectAll, selections }">
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
@@ -72,8 +69,8 @@
|
||||
</ListSelectBanner>
|
||||
</ListView>
|
||||
</div>
|
||||
<div v-else class="text-sm italic text-ink-gray-5">
|
||||
{{ __('No Assessments') }}
|
||||
<div v-else class="text-ink-gray-7">
|
||||
{{ __('No assessments added to this batch') }}
|
||||
</div>
|
||||
</div>
|
||||
<AssessmentModal
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Courses') }}
|
||||
</div>
|
||||
<Button v-if="canSeeAddButton()" @click="openCourseModal()">
|
||||
<Button v-if="isAdmin()" @click="openCourseModal()">
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4" />
|
||||
</template>
|
||||
@@ -15,7 +15,7 @@
|
||||
<ListView
|
||||
:columns="getCoursesColumns()"
|
||||
:rows="courses.data"
|
||||
row-key="batch_course"
|
||||
row-key="name"
|
||||
class="border rounded-lg"
|
||||
:options="{
|
||||
showTooltip: false,
|
||||
@@ -33,7 +33,7 @@
|
||||
</ListHeaderItem>
|
||||
</ListHeader>
|
||||
<ListRows>
|
||||
<ListRow :row="row" v-for="row in courses.data">
|
||||
<ListRow :row="row" v-for="row in courses.data" class="!rounded-none">
|
||||
<template #default="{ column, item }">
|
||||
<ListRowItem :item="row[column.key]" :align="column.align">
|
||||
<div>
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
</ListRow>
|
||||
</ListRows>
|
||||
<ListSelectBanner>
|
||||
<ListSelectBanner class="!min-w-0">
|
||||
<template #actions="{ unselectAll, selections }">
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
@@ -57,21 +57,20 @@
|
||||
</ListSelectBanner>
|
||||
</ListView>
|
||||
</div>
|
||||
<div v-else class="text-sm italic text-ink-gray-5">
|
||||
{{ __('No courses added') }}
|
||||
<div v-else class="text-ink-gray-7">
|
||||
{{ __('No courses added to this batch') }}
|
||||
</div>
|
||||
<BatchCourseModal
|
||||
v-model="showCourseModal"
|
||||
:batch="batch"
|
||||
:batch="batch.data?.name"
|
||||
v-model:courses="courses"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, inject } from 'vue'
|
||||
import { ref, inject, nextTick } from 'vue'
|
||||
import BatchCourseModal from '@/components/Modals/BatchCourseModal.vue'
|
||||
import {
|
||||
createResource,
|
||||
createListResource,
|
||||
Button,
|
||||
ListHeader,
|
||||
@@ -91,7 +90,7 @@ const user = inject('$user')
|
||||
|
||||
const props = defineProps({
|
||||
batch: {
|
||||
type: String,
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
@@ -99,11 +98,12 @@ const props = defineProps({
|
||||
const courses = createListResource({
|
||||
doctype: 'Batch Course',
|
||||
filters: {
|
||||
parent: props.batch,
|
||||
parent: props.batch.data?.name,
|
||||
parenttype: 'LMS Batch',
|
||||
},
|
||||
fields: ['name', 'course', 'title', 'evaluator'],
|
||||
parent: 'LMS Batch',
|
||||
orderBy: 'idx',
|
||||
auto: true,
|
||||
})
|
||||
|
||||
@@ -125,32 +125,16 @@ const getCoursesColumns = () => {
|
||||
]
|
||||
}
|
||||
|
||||
const deleteCourses = createResource({
|
||||
url: 'lms.lms.api.delete_documents',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'Batch Course',
|
||||
documents: values.courses,
|
||||
}
|
||||
},
|
||||
})
|
||||
const removeCourses = async (selections, unselectAll) => {
|
||||
for (const course of selections) {
|
||||
await courses.delete.submit(course)
|
||||
}
|
||||
|
||||
const removeCourses = (selections, unselectAll) => {
|
||||
deleteCourses.submit(
|
||||
{
|
||||
courses: Array.from(selections),
|
||||
},
|
||||
{
|
||||
onSuccess(data) {
|
||||
courses.reload()
|
||||
toast.success(__('Courses deleted successfully'))
|
||||
unselectAll()
|
||||
},
|
||||
}
|
||||
)
|
||||
unselectAll()
|
||||
toast.success(__('Courses deleted successfully'))
|
||||
}
|
||||
|
||||
const canSeeAddButton = () => {
|
||||
const isAdmin = () => {
|
||||
if (readOnlyMode) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="space-y-10 p-5">
|
||||
<div class="space-y-10 mt-5 w-[75%] mx-auto">
|
||||
<UpcomingEvaluations
|
||||
:batch="batch.data.name"
|
||||
:endDate="batch.data.evaluation_end_date"
|
||||
:courses="batch.data.courses"
|
||||
/>
|
||||
<BatchCourses :batch="batch" />
|
||||
<Assessments :batch="batch.data.name" />
|
||||
<BatchCourses :batch="batch.data.name" />
|
||||
<div class="grid grid-cols-2 gap-10"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
batch.data.accept_enrollments
|
||||
"
|
||||
>
|
||||
<Button v-if="!isStudent" class="w-full mt-4" variant="solid">
|
||||
<Button v-if="!canAccessBatch" class="w-full mt-4" variant="solid">
|
||||
<template #prefix>
|
||||
<CreditCard class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="liveClasses.data?.length"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5 mt-5"
|
||||
>
|
||||
<div
|
||||
v-for="cls in liveClasses.data"
|
||||
|
||||
199
frontend/src/pages/Batches/components/NewBatchModal.vue
Normal file
199
frontend/src/pages/Batches/components/NewBatchModal.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
title: __('New Batch'),
|
||||
size: '3xl',
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="text-base">
|
||||
<div class="grid grid-cols-2 gap-10">
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batch.title"
|
||||
:label="__('Title')"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.start_date"
|
||||
:label="__('Start Date')"
|
||||
type="date"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.end_date"
|
||||
:label="__('End Date')"
|
||||
type="date"
|
||||
:required="true"
|
||||
/>
|
||||
<Link
|
||||
doctype="LMS Category"
|
||||
v-model="batch.category"
|
||||
:label="__('Category')"
|
||||
:allowCreate="true"
|
||||
@create="
|
||||
() => {
|
||||
openSettings('Categories')
|
||||
show = false
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="batch.start_time"
|
||||
:label="__('Start Time')"
|
||||
type="time"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.end_time"
|
||||
:label="__('End Time')"
|
||||
type="time"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.timezone"
|
||||
:label="__('Timezone')"
|
||||
:required="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.seat_count"
|
||||
:label="__('Seat Count')"
|
||||
type="number"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5 border-t mt-5 pt-5">
|
||||
<MultiSelect
|
||||
v-model="batch.instructors"
|
||||
doctype="Course Evaluator"
|
||||
:label="__('Instructors')"
|
||||
:required="true"
|
||||
:onCreate="(close: () => void) => openSettings('Evaluators', close)"
|
||||
:filters="{ ignore_user_type: 1 }"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="batch.description"
|
||||
:label="__('Description')"
|
||||
type="textarea"
|
||||
:required="true"
|
||||
:rows="4"
|
||||
/>
|
||||
<div class="">
|
||||
<div class="mb-1.5 text-sm text-ink-gray-5">
|
||||
{{ __('Batch Details') }}
|
||||
<span class="text-ink-red-3">*</span>
|
||||
</div>
|
||||
<TextEditor
|
||||
:content="batch.batch_details"
|
||||
@change="(val: string) => (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-[10rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ close }">
|
||||
<div class="text-right">
|
||||
<Button variant="solid" @click="saveBatch(close)">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
|
||||
import { Link, useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
||||
import { ref, inject, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { cleanError, openSettings } from '@/utils'
|
||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||
|
||||
const show = defineModel<boolean>({ required: true, default: false })
|
||||
const router = useRouter()
|
||||
const { capture } = useTelemetry()
|
||||
const { updateOnboardingStep } = useOnboarding('learning')
|
||||
const user = inject<any>('$user')
|
||||
|
||||
const props = defineProps<{
|
||||
batches: any
|
||||
}>()
|
||||
|
||||
const batch = ref({
|
||||
title: '',
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
timezone: null,
|
||||
description: '',
|
||||
batch_details: '',
|
||||
instructors: [],
|
||||
category: null,
|
||||
seat_count: 0,
|
||||
})
|
||||
|
||||
const saveBatch = (close: () => void = () => {}) => {
|
||||
props.batches.insert.submit(
|
||||
{
|
||||
...batch.value,
|
||||
instructors: batch.value.instructors.map((instructor) => ({
|
||||
instructor: instructor,
|
||||
})),
|
||||
},
|
||||
{
|
||||
onSuccess(data: any) {
|
||||
toast.success(__('Batch created successfully'))
|
||||
close()
|
||||
capture('batch_created')
|
||||
router.push({
|
||||
name: 'BatchDetail',
|
||||
params: { batchName: data.name },
|
||||
hash: '#settings',
|
||||
})
|
||||
if (user.data?.is_system_manager) {
|
||||
updateOnboardingStep('create_first_batch', true, false, () => {
|
||||
localStorage.setItem('firstBatch', data.name)
|
||||
})
|
||||
}
|
||||
},
|
||||
onError(err: any) {
|
||||
toast.error(cleanError(err.messages?.[0]))
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const keyboardShortcut = (e: KeyboardEvent) => {
|
||||
if (
|
||||
e.key === 's' &&
|
||||
(e.ctrlKey || e.metaKey) &&
|
||||
e.target &&
|
||||
e.target instanceof HTMLElement &&
|
||||
!e.target.classList.contains('ProseMirror')
|
||||
) {
|
||||
saveBatch()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
watch(show, () => {
|
||||
if (show.value) capture('batch_form_opened')
|
||||
})
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
title: __('Create Course'),
|
||||
title: __('New Course'),
|
||||
size: '3xl',
|
||||
}"
|
||||
>
|
||||
@@ -67,7 +67,7 @@
|
||||
<template #actions="{ close }">
|
||||
<div class="text-right">
|
||||
<Button variant="solid" @click="saveCourse(close)">
|
||||
{{ __('Create') }}
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user