feat: question update

This commit is contained in:
Jannat Patel
2024-08-09 20:38:14 +05:30
parent a3a3085b1f
commit 685e09ce4b
5 changed files with 189 additions and 114 deletions

View File

@@ -91,8 +91,7 @@
<div> <div>
{{ option.label }} {{ option.label }}
</div> </div>
<div class="text-xs text-gray-700"> <div class="text-xs text-gray-700" v-html="option.description">
{{ option.description }}
</div> </div>
</div> </div>
</slot> </slot>

View File

@@ -21,8 +21,8 @@
</template> </template>
<script setup> <script setup>
import { Dialog, FormControl, createResource } from 'frappe-ui' import { Dialog, FormControl, createResource } from 'frappe-ui'
import { defineModel, reactive, watch, inject } from 'vue' import { defineModel, reactive, watch } from 'vue'
import { createToast, formatTime } from '@/utils/' import { createToast } from '@/utils/'
const show = defineModel() const show = defineModel()
const outline = defineModel('outline') const outline = defineModel('outline')

View File

@@ -138,8 +138,8 @@ const props = defineProps({
type: String, type: String,
default: __('Add a new question'), default: __('Add a new question'),
}, },
questionName: { questionDetail: {
type: [String, null], type: [Object, null],
required: true, required: true,
}, },
}) })
@@ -149,11 +149,10 @@ const questionData = createResource({
makeParams() { makeParams() {
return { return {
doctype: 'LMS Question', doctype: 'LMS Question',
name: props.questionName, name: props.questionDetail.question,
} }
}, },
auto: false, auto: false,
cache: ['question', props.questionName],
onSuccess(data) { onSuccess(data) {
let counter = 1 let counter = 1
editMode.value = true editMode.value = true
@@ -166,17 +165,35 @@ const questionData = createResource({
: false : false
counter++; counter++;
} }
question.marks = props.questionDetail.marks
}, },
}); });
watch(show, () => { watch(show, () => {
if (show.value && props.questionName) questionData.fetch() if (show.value) {
editMode.value = false
if (props.questionDetail.question)
questionData.fetch()
else {
question.question = "",
question.marks = 0
question.type = "Choices"
existingQuestion.question = ""
existingQuestion.marks = 0
questionType.value = null
populateFields()
}
if (props.questionDetail.marks)
question.marks = props.questionDetail.marks
}
}) })
const questionRow = createResource({ const questionRow = createResource({
url: 'frappe.client.insert', url: 'frappe.client.insert',
makeParams(values) { makeParams(values) {
console.log(values)
return { return {
doc: { doc: {
doctype: 'LMS Quiz Question', doctype: 'LMS Quiz Question',
@@ -270,20 +287,38 @@ const questionUpdate = createResource({
} }
}) })
const marksUpdate = createResource({
url: 'frappe.client.set_value',
auto: false,
makeParams(values) {
return {
doctype: 'LMS Quiz Question',
name: props.questionDetail.name,
fieldname: {
marks: question.marks,
},
}
}
})
const updateQuestion = (close) => { const updateQuestion = (close) => {
questionUpdate.submit( questionUpdate.submit(
{}, {},
{ {
onSuccess() { onSuccess() {
show.value = false marksUpdate.submit({}, {
showToast(__('Success'), __('Question updated successfully'), 'check') onSuccess() {
quiz.value.reload() show.value = false
close() showToast(__('Success'), __('Question updated successfully'), 'check')
}, quiz.value.reload()
onError(err) { close()
showToast(__('Error'), __(err.message?.[0] || err), 'x') },
close() onError(err) {
}, showToast(__('Error'), __(err.message?.[0] || err), 'x')
close()
},
})
}
} }
) )
} }

View File

@@ -13,7 +13,6 @@
<div class="text-sm font-semibold mb-4"> <div class="text-sm font-semibold mb-4">
{{ __('Details') }} {{ __('Details') }}
</div> </div>
{{ quiz }}
<FormControl <FormControl
v-model="quiz.title" v-model="quiz.title"
:label=" :label="
@@ -23,28 +22,20 @@
" "
/> />
<div v-if="quizDetails.data?.name"> <div v-if="quizDetails.data?.name">
<div class="grid grid-cols-2 gap-5 mt-2 mb-8"> <div class="grid grid-cols-3 gap-5 mt-2 mb-8">
<div class="space-y-2"> <FormControl
<FormControl v-model="quiz.max_attempts"
v-model="quiz.max_attempts" :label="__('Maximun Attempts')"
:label="__('Maximun Attempts')" />
/> <FormControl
<FormControl v-model="quiz.total_marks"
v-model="quiz.limit_questions_to" :label="__('Total Marks')"
:label="__('Limit Questions To')" disabled
/> />
</div> <FormControl
<div class="space-y-2"> v-model="quiz.passing_percentage"
<FormControl :label="__('Passing Percentage')"
v-model="quiz.total_marks" />
:label="__('Total Marks')"
disabled
/>
<FormControl
v-model="quiz.passing_percentage"
:label="__('Passing Percentage')"
/>
</div>
</div> </div>
<!-- Settings --> <!-- Settings -->
@@ -63,14 +54,29 @@
type="checkbox" type="checkbox"
:label="__('Show Submission History')" :label="__('Show Submission History')"
/> />
</div>
</div>
<div class="mb-8">
<div class="text-sm font-semibold mb-4">
{{ __('Shuffle Settings') }}
</div>
<div class="grid grid-cols-3">
<FormControl <FormControl
v-model="quiz.shuffle_questions" v-model="quiz.shuffle_questions"
type="checkbox" type="checkbox"
:label="__('Shuffle Questions')" :label="__('Shuffle Questions')"
/> />
<FormControl v-if="quiz.shuffle_questions"
v-model="quiz.limit_questions_to"
:label="__('Limit Questions To')"
/>
</div> </div>
</div> </div>
<!-- Questions --> <!-- Questions -->
<div> <div>
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
@@ -102,12 +108,12 @@
:row="row" :row="row"
v-slot="{ idx, column, item }" v-slot="{ idx, column, item }"
v-for="row in quiz.questions" v-for="row in quiz.questions"
@click="openQuestionModal(row.question)" @click="openQuestionModal(row)"
> >
<ListRowItem :item="item"> <ListRowItem :item="item">
<div <div
v-if="column.key == 'question_detail'" v-if="column.key == 'question_detail'"
class="text-xs truncate" v-html="item" class="text-xs truncate h-4" v-html="item"
> >
</div> </div>
<div v-else class="text-xs"> <div v-else class="text-xs">
@@ -116,6 +122,18 @@
</ListRowItem> </ListRowItem>
</ListRow> </ListRow>
</ListRows> </ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="deleteQuizzes(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView> </ListView>
</div> </div>
</div> </div>
@@ -123,10 +141,10 @@
</div> </div>
<Question <Question
v-model="showQuestionModal" v-model="showQuestionModal"
:questionName="currentQuestion" :questionDetail="currentQuestion"
v-model:quiz="quizDetails" v-model:quiz="quizDetails"
:title=" :title="
currentQuestion ? __('Edit the question') : __('Add a new question') currentQuestion.question ? __('Edit the question') : __('Add a new question')
" "
/> />
</template> </template>
@@ -141,6 +159,7 @@ import {
ListRows, ListRows,
ListRow, ListRow,
ListRowItem, ListRowItem,
ListSelectBanner,
Button, Button,
} from 'frappe-ui' } from 'frappe-ui'
import { import {
@@ -151,47 +170,22 @@ import {
inject, inject,
onBeforeUnmount, onBeforeUnmount,
watch, watch,
isReactive,
} from 'vue' } from 'vue'
import { Plus } from 'lucide-vue-next' import { Plus, Trash2 } from 'lucide-vue-next'
import Question from '@/components/Modals/Question.vue' import Question from '@/components/Modals/Question.vue'
import { showToast } from '../utils' import { showToast } from '../utils'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const showQuestionModal = ref(false) const showQuestionModal = ref(false)
const currentQuestion = ref(null) const currentQuestion = reactive({
question: '',
marks: 0,
name: ''
})
const user = inject('$user') const user = inject('$user')
const router = useRouter() const router = useRouter()
onMounted(() => {
if (
props.quizID == 'new' &&
!user.data?.is_moderator &&
!user.data?.is_instructor
) {
router.push({ name: 'Courses' })
}
if (props.quizID !== 'new') {
console.log("mounted")
quizDetails.reload()
}
window.addEventListener('keydown', keyboardShortcut)
})
const keyboardShortcut = (e) => {
if (
e.key === 's' &&
(e.ctrlKey || e.metaKey) &&
!e.target.classList.contains('ProseMirror')
) {
submitQuiz()
e.preventDefault()
}
}
onBeforeUnmount(() => {
window.removeEventListener('keydown', keyboardShortcut)
})
const props = defineProps({ const props = defineProps({
quizID: { quizID: {
type: String, type: String,
@@ -211,16 +205,51 @@ const quiz = reactive({
questions: [], questions: [],
}) })
onMounted(() => {
if (
props.quizID == 'new' &&
!user.data?.is_moderator &&
!user.data?.is_instructor
) {
router.push({ name: 'Courses' })
}
if (props.quizID !== 'new') {
quizDetails.reload()
}
window.addEventListener('keydown', keyboardShortcut)
})
const keyboardShortcut = (e) => {
if (
e.key === 's' &&
(e.ctrlKey || e.metaKey) &&
!e.target.classList.contains('ProseMirror')
) {
submitQuiz()
e.preventDefault()
}
}
onBeforeUnmount(() => {
window.removeEventListener('keydown', keyboardShortcut)
})
watch(
() => props.quizID !== 'new',
(newVal) => {
if (newVal) {
quizDetails.reload()
}
}
)
const quizDetails = createResource({ const quizDetails = createResource({
url: 'frappe.client.get', url: 'frappe.client.get',
makeParams(values) { makeParams(values) {
return { doctype: 'LMS Quiz', name: props.quizID } return { doctype: 'LMS Quiz', name: props.quizID }
}, },
cache: ['quiz', props.quizID],
auto: false, auto: false,
onSuccess(data) { onSuccess(data) {
console.log(data)
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
if (Object.hasOwn(quiz, key)) quiz[key] = data[key] if (Object.hasOwn(quiz, key)) quiz[key] = data[key]
}) })
@@ -234,7 +263,6 @@ const quizDetails = createResource({
let key = checkboxes[idx] let key = checkboxes[idx]
quiz[key] = quiz[key] ? true : false quiz[key] = quiz[key] ? true : false
} }
console.log(quiz)
}, },
}) })
@@ -276,7 +304,7 @@ const createQuiz = () => {
{}, {},
{ {
onSuccess(data) { onSuccess(data) {
showToast(__('Success'), __('Quiz created successfully', 'check')) showToast(__('Success'), __('Quiz created successfully'), 'check')
router.push({ router.push({
name: 'QuizCreation', name: 'QuizCreation',
params: { quizID: data.name }, params: { quizID: data.name },
@@ -334,22 +362,40 @@ const questionColumns = computed(() => {
] ]
}) })
watch(
() => props.quizID !== 'new',
(newVal) => {
console.log(props.quizID)
if (newVal) {
console.log("in watch")
quizDetails.reload()
}
}
)
const openQuestionModal = (question = null) => { const openQuestionModal = (question = null) => {
currentQuestion.value = question if (question) {
currentQuestion.question = question.question
currentQuestion.marks = question.marks
currentQuestion.name = question.name
} else {
currentQuestion.question = ''
currentQuestion.marks = 0
currentQuestion.name = ''
}
showQuestionModal.value = true showQuestionModal.value = true
} }
const deleteQuiz = createResource({
url: 'frappe.client.delete',
makeParams(values) {
return {
doctype: 'LMS Quiz Question',
name: values.quiz,
}
},
})
const deleteQuizzes = (selections, unselectAll) => {
selections.forEach(async (quiz) => {
deleteQuiz.submit({ quiz })
})
setTimeout(() => {
quizDetails.reload()
unselectAll()
}, 500)
}
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let crumbs = [ let crumbs = [
{ {

View File

@@ -9,16 +9,15 @@
"field_order": [ "field_order": [
"title", "title",
"max_attempts", "max_attempts",
"limit_questions_to", "show_answers",
"column_break_gaac", "column_break_gaac",
"total_marks", "total_marks",
"passing_percentage", "passing_percentage",
"section_break_hsiv",
"show_answers",
"column_break_rocd",
"show_submission_history", "show_submission_history",
"column_break_dsup", "section_break_tzbu",
"shuffle_questions", "shuffle_questions",
"column_break_clsh",
"limit_questions_to",
"section_break_sbjx", "section_break_sbjx",
"questions", "questions",
"section_break_3", "section_break_3",
@@ -91,11 +90,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show Submission History" "label": "Show Submission History"
}, },
{
"fieldname": "section_break_hsiv",
"fieldtype": "Section Break",
"label": "Settings"
},
{ {
"fieldname": "passing_percentage", "fieldname": "passing_percentage",
"fieldtype": "Int", "fieldtype": "Int",
@@ -105,10 +99,6 @@
"non_negative": 1, "non_negative": 1,
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "column_break_rocd",
"fieldtype": "Column Break"
},
{ {
"default": "0", "default": "0",
"fieldname": "total_marks", "fieldname": "total_marks",
@@ -119,10 +109,6 @@
"read_only": 1, "read_only": 1,
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "column_break_dsup",
"fieldtype": "Column Break"
},
{ {
"default": "0", "default": "0",
"fieldname": "shuffle_questions", "fieldname": "shuffle_questions",
@@ -130,14 +116,23 @@
"label": "Shuffle Questions" "label": "Shuffle Questions"
}, },
{ {
"depends_on": "shuffle_questions",
"fieldname": "limit_questions_to", "fieldname": "limit_questions_to",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Limit Questions To" "label": "Limit Questions To"
},
{
"fieldname": "section_break_tzbu",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_clsh",
"fieldtype": "Column Break"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2024-07-19 18:21:26.681501", "modified": "2024-08-09 12:21:36.256522",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Quiz", "name": "LMS Quiz",