fix: course creation form validations
This commit is contained in:
@@ -55,7 +55,7 @@
|
|||||||
{{ resume.file_name }}
|
{{ resume.file_name }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-gray-500 mt-1">
|
<span class="text-sm text-gray-500 mt-1">
|
||||||
{{ getFileSize() }}
|
{{ getFileSize(resume.file_size) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
import { Dialog, FileUploader, Button, createResource } from 'frappe-ui'
|
import { Dialog, FileUploader, Button, createResource } from 'frappe-ui'
|
||||||
import { FileText } from 'lucide-vue-next'
|
import { FileText } from 'lucide-vue-next'
|
||||||
import { ref, inject, defineModel } from 'vue'
|
import { ref, inject, defineModel } from 'vue'
|
||||||
import { createToast } from '@/utils/'
|
import { createToast, getFileSize } from '@/utils/'
|
||||||
|
|
||||||
const resume = ref(null)
|
const resume = ref(null)
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
@@ -87,16 +87,6 @@ const validateFile = (file) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileSize = () => {
|
|
||||||
let value = parseInt(resume.value.file_size)
|
|
||||||
if (value > 1048576) {
|
|
||||||
return (value / 1048576).toFixed(2) + 'M'
|
|
||||||
} else if (value > 1024) {
|
|
||||||
return (value / 1024).toFixed(2) + 'K'
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobApplication = createResource({
|
const jobApplication = createResource({
|
||||||
url: 'frappe.client.insert',
|
url: 'frappe.client.insert',
|
||||||
makeParams(values) {
|
makeParams(values) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { sessionStore } from '@/stores/session'
|
|||||||
import { Dropdown } from 'frappe-ui'
|
import { Dropdown } from 'frappe-ui'
|
||||||
import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next'
|
import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { convertToTitleCase } from '../utils'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -94,18 +95,4 @@ const userDropdownOptions = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function convertToTitleCase(str) {
|
|
||||||
if (!str) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
|
||||||
.toLowerCase()
|
|
||||||
.split(' ')
|
|
||||||
.map(function (word) {
|
|
||||||
return word.charAt(0).toUpperCase().concat(word.substr(1))
|
|
||||||
})
|
|
||||||
.join(' ')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,11 +25,15 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</header>
|
</header>
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<Input v-model="course.title" :label="__('Title')" class="mb-2" />
|
<FormControl
|
||||||
<Input
|
v-model="course.title"
|
||||||
|
:label="__('Title')"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
v-model="course.short_introduction"
|
v-model="course.short_introduction"
|
||||||
:label="__('Short Introduction')"
|
:label="__('Short Introduction')"
|
||||||
class="mb-2"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="mb-1.5 text-sm text-gray-700">
|
<div class="mb-1.5 text-sm text-gray-700">
|
||||||
@@ -37,35 +41,81 @@
|
|||||||
</div>
|
</div>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
:content="course.description"
|
:content="course.description"
|
||||||
@change="(val) => (topic.reply = val)"
|
@change="(val) => (course.description = val)"
|
||||||
:editable="true"
|
:editable="true"
|
||||||
:fixedMenu="true"
|
:fixedMenu="true"
|
||||||
editorClass="prose-sm max-w-none border-b border-x bg-gray-100 rounded-b-md py-1 px-2 min-h-[7rem]"
|
editorClass="prose-sm max-w-none border-b border-x bg-gray-100 rounded-b-md py-1 px-2 min-h-[7rem]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<FormControl
|
||||||
v-model="course.video_link"
|
v-model="course.video_link"
|
||||||
:label="__('Preview Video')"
|
:label="__('Preview Video')"
|
||||||
class="mb-2"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
<Input v-model="course.tags" :label="__('Tags')" class="mb-2" />
|
<FileUploader
|
||||||
|
v-if="!course.image"
|
||||||
|
:fileTypes="['image/*']"
|
||||||
|
:validateFile="validateFile"
|
||||||
|
@success="
|
||||||
|
(file) => {
|
||||||
|
console.log(file)
|
||||||
|
course.image = file
|
||||||
|
console.log(course.image)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template v-slot="{ file, progress, uploading, openFileSelector }">
|
||||||
|
<div class="mb-4">
|
||||||
|
<Button @click="openFileSelector" :loading="uploading">
|
||||||
|
{{ uploading ? `Uploading ${progress}%` : 'Upload an image' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FileUploader>
|
||||||
|
<div v-else class="flex items-center">
|
||||||
|
<div class="border rounded-md p-2 mr-2">
|
||||||
|
<FileText class="h-5 w-5 stroke-1.5 text-gray-700" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span>
|
||||||
|
{{ course.image }}
|
||||||
|
</span>
|
||||||
|
<span class="text-sm text-gray-500 mt-1">
|
||||||
|
{{ getFileSize(course.image) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormControl v-model="course.tags" :label="__('Tags')" class="mb-4" />
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<Checkbox v-model="course.published" :label="__('Published')" />
|
<FormControl
|
||||||
<Checkbox
|
type="checkbox"
|
||||||
|
v-model="course.published"
|
||||||
|
:label="__('Published')"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
type="checkbox"
|
||||||
v-model="course.upcoming"
|
v-model="course.upcoming"
|
||||||
:label="__('Upcoming')"
|
:label="__('Upcoming')"
|
||||||
class="ml-20"
|
class="ml-20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<div class="mb-4">
|
||||||
v-model="course.paid_course"
|
<FormControl
|
||||||
:label="__('Paid Course')"
|
type="checkbox"
|
||||||
class="mb-2"
|
v-model="course.paid_course"
|
||||||
/>
|
:label="__('Paid Course')"
|
||||||
<Input
|
/>
|
||||||
|
</div>
|
||||||
|
<FormControl
|
||||||
v-model="course.course_price"
|
v-model="course.course_price"
|
||||||
:label="__('Course Price')"
|
:label="__('Course Price')"
|
||||||
class="mb-2"
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
doctype="Currency"
|
||||||
|
v-model="course.currency"
|
||||||
|
:filters="{ enabled: 1 }"
|
||||||
|
:label="__('Currency')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,14 +126,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Input,
|
|
||||||
TextEditor,
|
TextEditor,
|
||||||
Checkbox,
|
|
||||||
Button,
|
Button,
|
||||||
createDocumentResource,
|
|
||||||
createResource,
|
createResource,
|
||||||
|
FormControl,
|
||||||
|
FileUploader,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { reactive, inject, onMounted } from 'vue'
|
import { reactive, inject, onMounted } from 'vue'
|
||||||
|
import { convertToTitleCase, createToast, getFileSize } from '../utils'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import { FileText } from 'lucide-vue-next'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
|
|
||||||
@@ -103,7 +155,7 @@ const course = reactive({
|
|||||||
upcoming: false,
|
upcoming: false,
|
||||||
image: null,
|
image: null,
|
||||||
paid_course: false,
|
paid_course: false,
|
||||||
course_price: 0,
|
course_price: null,
|
||||||
currency: '',
|
currency: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -118,14 +170,48 @@ const courseResource = createResource({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
console.log(courseResource)
|
|
||||||
|
|
||||||
const submitCourse = () => {
|
const submitCourse = () => {
|
||||||
courseResource.submit(
|
courseResource.submit(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
validate() {},
|
validate() {
|
||||||
|
const mandatory_fields = [
|
||||||
|
'title',
|
||||||
|
'short_introduction',
|
||||||
|
'description',
|
||||||
|
'video_link',
|
||||||
|
'image',
|
||||||
|
]
|
||||||
|
for (const field of mandatory_fields) {
|
||||||
|
if (!course[field]) {
|
||||||
|
let fieldLabel = convertToTitleCase(field.split('_').join(' '))
|
||||||
|
return `${fieldLabel} is mandatory`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (course.paid_course && (!course.course_price || !course.currency)) {
|
||||||
|
return 'Course price and currency are mandatory for paid courses'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
createToast({
|
||||||
|
title: 'Error',
|
||||||
|
text: err.messages?.[0] || err,
|
||||||
|
icon: 'x',
|
||||||
|
iconClasses: 'bg-red-600 text-white rounded-md p-px',
|
||||||
|
position: 'top-center',
|
||||||
|
timeout: 10,
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateFile = (file) => {
|
||||||
|
console.log(file)
|
||||||
|
let extension = file.name.split('.').pop().toLowerCase()
|
||||||
|
if (!['jpg', 'jpeg', 'png'].includes(extension)) {
|
||||||
|
return 'Only image file is allowed.'
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -41,6 +41,29 @@ export function formatNumberIntoCurrency(number, currency) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertToTitleCase(str) {
|
||||||
|
if (!str) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
.toLowerCase()
|
||||||
|
.split(' ')
|
||||||
|
.map(function (word) {
|
||||||
|
return word.charAt(0).toUpperCase().concat(word.substr(1))
|
||||||
|
})
|
||||||
|
.join(' ')
|
||||||
|
}
|
||||||
|
export function getFileSize(file_size) {
|
||||||
|
let value = parseInt(file_size)
|
||||||
|
if (value > 1048576) {
|
||||||
|
return (value / 1048576).toFixed(2) + 'M'
|
||||||
|
} else if (value > 1024) {
|
||||||
|
return (value / 1024).toFixed(2) + 'K'
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
export function getTimezones() {
|
export function getTimezones() {
|
||||||
return [
|
return [
|
||||||
'Pacific/Midway',
|
'Pacific/Midway',
|
||||||
|
|||||||
@@ -75,7 +75,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "video_link",
|
"fieldname": "video_link",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Video Embed Link"
|
"label": "Video Embed Link",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "short_introduction",
|
"fieldname": "short_introduction",
|
||||||
@@ -92,7 +93,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach Image",
|
"fieldtype": "Attach Image",
|
||||||
"label": "Preview Image"
|
"label": "Preview Image",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tags",
|
"fieldname": "tags",
|
||||||
@@ -266,7 +268,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-12-21 12:27:32.559901",
|
"modified": "2024-02-28 11:20:47.700649",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
|
|||||||
Reference in New Issue
Block a user